--- /dev/null
+Like other projects, we also have some guidelines to keep to the
+code. For git in general, three rough rules are:
+
+ - Most importantly, we never say "It's in POSIX; we'll happily
+ ignore your needs should your system not conform to it."
+ We live in the real world.
+
+ - However, we often say "Let's stay away from that construct,
+ it's not even in POSIX".
+
+ - In spite of the above two rules, we sometimes say "Although
+ this is not in POSIX, it (is so convenient | makes the code
+ much more readable | has other good characteristics) and
+ practically all the platforms we care about support it, so
+ let's use it".
+
+ Again, we live in the real world, and it is sometimes a
+ judgement call, the decision based more on real world
+ constraints people face than what the paper standard says.
+
+
+As for more concrete guidelines, just imitate the existing code
+(this is a good guideline, no matter which project you are
+contributing to). But if you must have a list of rules,
+here they are.
+
+For shell scripts specifically (not exhaustive):
+
+ - We prefer $( ... ) for command substitution; unlike ``, it
+ properly nests. It should have been the way Bourne spelled
+ it from day one, but unfortunately isn't.
+
+ - We use ${parameter-word} and its [-=?+] siblings, and their
+ colon'ed "unset or null" form.
+
+ - We use ${parameter#word} and its [#%] siblings, and their
+ doubled "longest matching" form.
+
+ - We use Arithmetic Expansion $(( ... )).
+
+ - No "Substring Expansion" ${parameter:offset:length}.
+
+ - No shell arrays.
+
+ - No strlen ${#parameter}.
+
+ - No regexp ${parameter/pattern/string}.
+
+ - We do not use Process Substitution <(list) or >(list).
+
+ - We prefer "test" over "[ ... ]".
+
+ - We do not write the noiseword "function" in front of shell
+ functions.
+
+For C programs:
+
+ - We use tabs to indent, and interpret tabs as taking up to
+ 8 spaces.
+
+ - We try to keep to at most 80 characters per line.
+
+ - When declaring pointers, the star sides with the variable
+ name, i.e. "char *string", not "char* string" or
+ "char * string". This makes it easier to understand code
+ like "char *string, c;".
+
+ - We avoid using braces unnecessarily. I.e.
+
+ if (bla) {
+ x = 1;
+ }
+
+ is frowned upon. A gray area is when the statement extends
+ over a few lines, and/or you have a lengthy comment atop of
+ it. Also, like in the Linux kernel, if there is a long list
+ of "else if" statements, it can make sense to add braces to
+ single line blocks.
+
+ - Try to make your code understandable. You may put comments
+ in, but comments invariably tend to stale out when the code
+ they were describing changes. Often splitting a function
+ into two makes the intention of the code much clearer.
+
+ - Double negation is often harder to understand than no negation
+ at all.
+
+ - Some clever tricks, like using the !! operator with arithmetic
+ constructs, can be extremely confusing to others. Avoid them,
+ unless there is a compelling reason to use them.
+
+ - Use the API. No, really. We have a strbuf (variable length
+ string), several arrays with the ALLOC_GROW() macro, a
+ path_list for sorted string lists, a hash map (mapping struct
+ objects) named "struct decorate", amongst other things.
+
+ - When you come up with an API, document it.
+
+ - The first #include in C files, except in platform specific
+ compat/ implementations, should be git-compat-util.h or another
+ header file that includes it, such as cache.h or builtin.h.
+
+ - If you are planning a new command, consider writing it in shell
+ or perl first, so that changes in semantics can be easily
+ changed and discussed. Many git commands started out like
+ that, and a few are still scripts.
+
+ - Avoid introducing a new dependency into git. This means you
+ usually should stay away from scripting languages not already
+ used in the git core command set (unless your command is clearly
+ separate from it, such as an importer to convert random-scm-X
+ repositories to git).
ASCIIDOC=asciidoc
ASCIIDOC_EXTRA =
-ifdef ASCIIDOC8
-ASCIIDOC_EXTRA += -a asciidoc7compatible
-endif
INSTALL?=install
RM ?= rm -f
DOC_REF = origin/man
-include ../config.mak.autogen
-include ../config.mak
+ifdef ASCIIDOC8
+ASCIIDOC_EXTRA += -a asciidoc7compatible
+endif
+ifdef DOCBOOK_XSL_172
+ASCIIDOC_EXTRA += -a docbook-xsl-172
+endif
+
#
# Please note that there is a minor bug in asciidoc.
# The version after 6.0.3 _will_ include the patch found here:
* "git-send-pack $remote frotz" segfaulted when there is nothing
named 'frotz' on the local end.
- * "git-rebase -interactive" did not handle its "--strategy" option
+ * "git-rebase --interactive" did not handle its "--strategy" option
properly.
--- /dev/null
+GIT v1.5.3.6 Release Notes
+==========================
+
+Fixes since v1.5.3.5
+--------------------
+
+ * git-cvsexportcommit handles root commits better;
+
+ * git-svn dcommit used to clobber when sending a series of
+ patches;
+
+ * git-grep sometimes refused to work when your index was
+ unmerged;
+
+ * Quite a lot of documentation clarifications.
+
+--
+exec >/var/tmp/1
+O=v1.5.3.5-32-gcb6c162
+echo O=`git describe refs/heads/maint`
+git shortlog --no-merges $O..refs/heads/maint
* Comes with much improved gitk.
- * git-reset is now built-in.
+ * "progress display" from many commands are a lot nicer to the
+ eye. Transfer commands show throughput data.
+
+ * git-reset is now built-in and its output can be squelched with -q.
* git-send-email can optionally talk over ssmtp and use SMTP-AUTH.
* Various Perforce importer updates.
+ * git-lost-found was deprecated in favor of git-fsck's --lost-found
+ option.
+
+ * git-svnimport was removed in favor of git-svn.
+
+ * git-bisect learned "skip" action to mark untestable commits.
+
+ * rename detection diff family, while detecting exact matches,
+ has been greatly optimized.
+
+ * Example update and post-receive hooks have been improved.
+
+ * In addition there are quite a few internal clean-ups. Notably
+
+ - many fork/exec have been replaced with run-command API,
+ brought from the msysgit effort.
+
+ - introduction and more use of the option parser API.
+
+ - enhancement and more use of the strbuf API.
+
+
Fixes since v1.5.3
------------------
--
exec >/var/tmp/1
-O=v1.5.3.4-450-g952a9e5
+O=v1.5.3.5-618-g5d4138a
echo O=`git describe refs/heads/master`
git shortlog --no-merges $O..refs/heads/master ^refs/heads/maint
Patch:
- use "git format-patch -M" to create the patch
- - send your patch to <git@vger.kernel.org>. If you use
- git-send-email(1), please test it first by sending
- email to yourself.
- do not PGP sign your patch
- do not attach your patch, but read in the mail
body, unless you cannot teach your mailer to
corrupt whitespaces.
- provide additional information (which is unsuitable for
the commit message) between the "---" and the diffstat
- - send the patch to the list (git@vger.kernel.org) and the
- maintainer (gitster@pobox.com).
- if you change, add, or remove a command line option or
make some other user interface change, the associated
documentation should be updated as well.
- if your name is not writable in ASCII, make sure that
you send off a message in the correct encoding.
+ - send the patch to the list (git@vger.kernel.org) and the
+ maintainer (gitster@pobox.com). If you use
+ git-send-email(1), please test it first by sending
+ email to yourself.
Long version:
endif::backend-docbook[]
ifdef::backend-docbook[]
+ifndef::docbook-xsl-172[]
# "unbreak" docbook-xsl v1.68 for manpages. v1.69 works with or without this.
+# v1.72 breaks with this because it replaces dots not in roff requests.
[listingblock]
<example><title>{title}</title>
<literallayout>
endif::doctype-manpage[]
</literallayout>
{title#}</example>
+endif::docbook-xsl-172[]
endif::backend-docbook[]
ifdef::doctype-manpage[]
use File::Compare qw(compare);
sub format_one {
- my ($out, $name) = @_;
+ my ($out, $nameattr) = @_;
+ my ($name, $attr) = @$nameattr;
my ($state, $description);
$state = 0;
open I, '<', "$name.txt" or die "No such file $name.txt";
die "No description found in $name.txt";
}
if (my ($verify_name, $text) = ($description =~ /^($name) - (.*)/)) {
- print $out "gitlink:$name\[1\]::\n";
- print $out "\t$text.\n\n";
+ print $out "gitlink:$name\[1\]::\n\t";
+ if ($attr) {
+ print $out "($attr) ";
+ }
+ print $out "$text.\n\n";
}
else {
die "Description does not match $name: $description";
next if /^#/;
chomp;
- my ($name, $cat) = /^(\S+)\s+(.*)$/;
- push @{$cmds{$cat}}, $name;
+ my ($name, $cat, $attr) = /^(\S+)\s+(.*?)(?:\s+(.*))?$/;
+ push @{$cmds{$cat}}, [$name, $attr];
}
for my $cat (qw(ancillaryinterrogators
git-init mainporcelain
git-instaweb ancillaryinterrogators
gitk mainporcelain
-git-local-fetch synchingrepositories
git-log mainporcelain
-git-lost-found ancillarymanipulators
+git-lost-found ancillarymanipulators deprecated
git-ls-files plumbinginterrogators
git-ls-remote plumbinginterrogators
git-ls-tree plumbinginterrogators
git-show-index plumbinginterrogators
git-show-ref plumbinginterrogators
git-sh-setup purehelpers
-git-ssh-fetch synchingrepositories
-git-ssh-upload synchingrepositories
git-stash mainporcelain
git-status mainporcelain
git-stripspace purehelpers
git-svn foreignscminterface
git-symbolic-ref plumbingmanipulators
git-tag mainporcelain
-git-tar-tree plumbinginterrogators
+git-tar-tree plumbinginterrogators deprecated
git-unpack-file plumbinginterrogators
git-unpack-objects plumbingmanipulators
git-update-index plumbingmanipulators
supported.
clean.requireForce::
- A boolean to make git-clean do nothing unless given -f or -n. Defaults
- to false.
+ A boolean to make git-clean do nothing unless given -f
+ or -n. Defaults to true.
color.branch::
A boolean to enable/disable color in the output of
pack from a push can make the push operation complete faster,
especially on slow filesystems.
+format.numbered::
+ A boolean which can enable sequence numbers in patch subjects.
+ Seting this option to "auto" will enable it only if there is
+ more than one patch. See --numbered option in
+ gitlink:git-format-patch[1].
+
format.headers::
Additional email headers to include in a patch to be submitted
by mail. See gitlink:git-format-patch[1].
environment, is `git show-branch`.
------------------------------------------------
-$ git show-branch --topo-order master mybranch
+$ git-show-branch --topo-order --more=1 master mybranch
* [master] Merge work in mybranch
! [mybranch] Some work.
--
- [master] Merge work in mybranch
*+ [mybranch] Some work.
+* [master^] Some fun.
------------------------------------------------
The first two lines indicate that it is showing the two branches
commits from the master branch. The string inside brackets
before the commit log message is a short name you can use to
name the commit. In the above example, 'master' and 'mybranch'
-are branch heads. 'master~1' is the first parent of 'master'
+are branch heads. 'master^' is the first parent of 'master'
branch head. Please see 'git-rev-parse' documentation if you
see more complex cases.
+[NOTE]
+Without the '--more=1' option, 'git-show-branch' would not output the
+'[master^]' commit, as '[mybranch]' commit is a common ancestor of
+both 'master' and 'mybranch' tips. Please see 'git-show-branch'
+documentation for details.
+
+[NOTE]
+If there were more commits on the 'master' branch after the merge, the
+merge commit itself would not be shown by 'git-show-branch' by
+default. You would need to provide '--sparse' option to make the
+merge commit visible in this case.
+
Now, let's pretend you are the one who did all the work in
`mybranch`, and the fruit of your hard work has finally been merged
to the `master` branch. Let's go back to `mybranch`, and run
that does not even support directory index would suffice. But
you must prepare your repository with `git-update-server-info`
to help dumb transport downloaders.
-+
-There are (confusingly enough) `git-ssh-fetch` and `git-ssh-upload`
-programs, which are 'commit walkers'; they outlived their
-usefulness when git Native and SSH transports were introduced,
-and are not used by `git pull` or `git push` scripts.
Once you fetch from the remote repository, you `merge` that
with your current branch.
and bring ourselves back to the pre-merge state:
------------
-$ git show-branch --more=3 master mybranch
+$ git show-branch --more=2 master mybranch
! [master] Merge work in mybranch
* [mybranch] Merge work in mybranch
--
This is the same `git-read-tree` command we have already seen,
but it takes three trees, unlike previous examples. This reads
the contents of each tree into different 'stage' in the index
-file (the first tree goes to stage 1, the second stage 2,
+file (the first tree goes to stage 1, the second to stage 2,
etc.). After reading three trees into three stages, the paths
that are the same in all three stages are 'collapsed' into stage
0. Also paths that are the same in two of three stages are
--------
gitlink:git-status[1]
gitlink:git-rm[1]
+gitlink:git-reset[1]
gitlink:git-mv[1]
gitlink:git-commit[1]
gitlink:git-update-index[1]
'--track' were given.
--no-track::
- When -b is given and a branch is created off a remote branch,
+ When a branch is created off a remote branch,
set up configuration so that git-pull will not retrieve data
from the remote branch, ignoring the branch.autosetupmerge
configuration variable.
--------
When recording your own work, the contents of modified files in
your working tree are temporarily stored to a staging area
-called the "index" with gitlink:git-add[1]. Removal
-of a file is staged with gitlink:git-rm[1]. After building the
-state to be committed incrementally with these commands, `git
-commit` (without any pathname parameter) is used to record what
+called the "index" with gitlink:git-add[1]. A file can be
+reverted back, only in the index but not in the working tree,
+to that of the last commit with `git-reset HEAD -- <file>`,
+which effectively reverts `git-add` and prevents the changes to
+this file from participating in the next commit. After building
+the state to be committed incrementally with these commands,
+`git commit` (without any pathname parameter) is used to record what
has been staged so far. This is the most basic form of the
command. An example:
SYNOPSIS
--------
-'git-cvsexportcommit' [-h] [-u] [-v] [-c] [-P] [-p] [-a] [-d cvsroot] [-f] [-m msgprefix] [PARENTCOMMIT] COMMITID
+'git-cvsexportcommit' [-h] [-u] [-v] [-c] [-P] [-p] [-a] [-d cvsroot] [-w cvsworkdir] [-f] [-m msgprefix] [PARENTCOMMIT] COMMITID
DESCRIPTION
Exports a commit from GIT to a CVS checkout, making it easier
to merge patches from a git repository into a CVS repository.
-Execute it from the root of the CVS working copy. GIT_DIR must be defined.
-See examples below.
+Specify the name of a CVS checkout using the -w switch or execute it
+from the root of the CVS working copy. In the latter case GIT_DIR must
+be defined. See examples below.
It does its best to do the safe thing, it will check that the files are
unchanged and up to date in the CVS checkout, and it will not autocommit
-u::
Update affected files from CVS repository before attempting export.
+-w::
+ Specify the location of the CVS checkout to use for the export. This
+ option does not require GIT_DIR to be set before execution if the
+ current directory is within a git repository.
+
-v::
Verbose.
$ cvs commit -F .msg <files>
------------
+Merge one patch into CVS (-c and -w options). The working directory is within the Git Repo::
++
+------------
+ $ git-cvsexportcommit -v -c -w ~/project_cvs_checkout <commit-sha1>
+------------
+
Merge pending patches into CVS automatically -- only if you really know what you are doing::
+
------------
Author
------
-Written by Martin Langhoff <martin@catalyst.net.nz>
+Written by Martin Langhoff <martin@catalyst.net.nz> and others.
Documentation
--------------
-Documentation by Martin Langhoff <martin@catalyst.net.nz>
+Documentation by Martin Langhoff <martin@catalyst.net.nz> and others.
GIT
---
SYNOPSIS
--------
[verse]
-'git-format-patch' [-n | -k] [-o <dir> | --stdout] [--thread]
+'git-format-patch' [-k] [-o <dir> | --stdout] [--thread]
[--attach[=<boundary>] | --inline[=<boundary>]]
[-s | --signoff] [<common diff options>]
+ [-n | --numbered | -N | --no-numbered]
[--start-number <n>] [--numbered-files]
[--in-reply-to=Message-Id] [--suffix=.<sfx>]
[--ignore-if-in-upstream]
-n|--numbered::
Name output in '[PATCH n/m]' format.
+-N|--no-numbered::
+ Name output in '[PATCH]' format.
+
--start-number <n>::
Start numbering the patches at <n> instead of 1.
CONFIGURATION
-------------
-You can specify extra mail header lines to be added to each
-message in the repository configuration. You can also specify
-new defaults for the subject prefix and file suffix.
+You can specify extra mail header lines to be added to each message
+in the repository configuration, new defaults for the subject prefix
+and file suffix, and number patches when outputting more than one.
------------
[format]
headers = "Organization: git-foo\n"
subjectprefix = CHANGE
suffix = .txt
+ numbered = auto
------------
DESCRIPTION
-----------
Acts as a filter, extracting the commit ID stored in archives created by
-git-tar-tree. It reads only the first 1024 bytes of input, thus its
+gitlink:git-archive[1]. It reads only the first 1024 bytes of input, thus its
runtime is not influenced by the size of <tarfile> very much.
If no commit ID is found, git-get-tar-commit-id quietly exists with a
return code of 1. This can happen if <tarfile> had not been created
-using git-tar-tree or if the first parameter of git-tar-tree had been
+using git-archive or if the <treeish> parameter of git-archive had been
a tree ID instead of a commit ID or tag.
+++ /dev/null
-git-local-fetch(1)
-==================
-
-NAME
-----
-git-local-fetch - Duplicate another git repository on a local system
-
-
-SYNOPSIS
---------
-[verse]
-'git-local-fetch' [-c] [-t] [-a] [-d] [-v] [-w filename] [--recover] [-l] [-s] [-n]
- commit-id path
-
-DESCRIPTION
------------
-THIS COMMAND IS DEPRECATED.
-
-Duplicates another git repository on a local system.
-
-OPTIONS
--------
--c::
- Get the commit objects.
--t::
- Get trees associated with the commit objects.
--a::
- Get all the objects.
--v::
- Report what is downloaded.
--s::
- Instead of regular file-to-file copying use symbolic links to the objects
- in the remote repository.
--l::
- Before attempting symlinks (if -s is specified) or file-to-file copying the
- remote objects, try to hardlink the remote objects into the local
- repository.
--n::
- Never attempt to file-to-file copy remote objects. Only useful with
- -s or -l command-line options.
-
--w <filename>::
- Writes the commit-id into the filename under $GIT_DIR/refs/<filename> on
- the local end after the transfer is complete.
-
---stdin::
- Instead of a commit id on the command line (which is not expected in this
- case), 'git-local-fetch' expects lines on stdin in the format
-
- <commit-id>['\t'<filename-as-in--w>]
-
---recover::
- Verify that everything reachable from target is fetched. Used after
- an earlier fetch is interrupted.
-
-Author
-------
-Written by Junio C Hamano <junkio@cox.net>
-
-Documentation
---------------
-Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
-
-GIT
----
-Part of the gitlink:git[7] suite
DESCRIPTION
-----------
+
+*NOTE*: this command is deprecated. Use gitlink:git-fsck[1] with
+the option '--lost-found' instead.
+
Finds dangling commits and tags from the object database, and
creates refs to them in the .git/lost-found/ directory. Commits and
tags that dereference to commits are stored in .git/lost-found/commit,
--------
[verse]
'git-push' [--all] [--dry-run] [--tags] [--receive-pack=<git-receive-pack>]
- [--repo=all] [-f | --force] [-v] [<repository> <refspec>...]
+ [--repo=all] [-f | --force] [-v | --verbose] [<repository> <refspec>...]
DESCRIPTION
-----------
transfer spends extra cycles to minimize the number of
objects to be sent and meant to be used on slower connection.
--v::
+-v, \--verbose::
Run verbosely.
include::urls-remotes.txt[]
Fetch updates for a named set of remotes in the repository as defined by
remotes.<group>. If a named group is not specified on the command line,
the configuration parameter remotes.default will get used; if
-remotes.default is not defined, all remotes which do not the
+remotes.default is not defined, all remotes which do not have the
configuration parameter remote.<name>.skipDefaultUpdate set to true will
be updated. (See gitlink:git-config[1]).
SYNOPSIS
--------
[verse]
-'git-reset' [--mixed | --soft | --hard] [<commit>]
-'git-reset' [--mixed] <commit> [--] <paths>...
+'git-reset' [--mixed | --soft | --hard] [-q] [<commit>]
+'git-reset' [--mixed] [-q] <commit> [--] <paths>...
DESCRIPTION
-----------
switched to. Any changes to tracked files in the working tree
since <commit> are lost.
+-q::
+ Be quiet, only report errors.
+
<commit>::
Commit to make the current HEAD.
is not set, this will be prompted for.
--suppress-from, --no-suppress-from::
- If this is set, do not add the From: address to the cc: list, if it
- shows up in a From: line.
+ If this is set, do not add the From: address to the cc: list.
Default is the value of 'sendemail.suppressfrom' configuration value;
if that is unspecified, default to --no-suppress-from.
+++ /dev/null
-git-ssh-fetch(1)
-================
-
-NAME
-----
-git-ssh-fetch - Fetch from a remote repository over ssh connection
-
-
-
-SYNOPSIS
---------
-'git-ssh-fetch' [-c] [-t] [-a] [-d] [-v] [-w filename] [--recover] commit-id url
-
-DESCRIPTION
------------
-THIS COMMAND IS DEPRECATED.
-
-Pulls from a remote repository over ssh connection, invoking
-git-ssh-upload on the other end. It functions identically to
-git-ssh-upload, aside from which end you run it on.
-
-
-OPTIONS
--------
-commit-id::
- Either the hash or the filename under [URL]/refs/ to
- pull.
-
--c::
- Get the commit objects.
--t::
- Get trees associated with the commit objects.
--a::
- Get all the objects.
--v::
- Report what is downloaded.
--w::
- Writes the commit-id into the filename under $GIT_DIR/refs/ on
- the local end after the transfer is complete.
-
-
-Author
-------
-Written by Daniel Barkalow <barkalow@iabervon.org>
-
-Documentation
---------------
-Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
-
-GIT
----
-Part of the gitlink:git[7] suite
+++ /dev/null
-git-ssh-upload(1)
-=================
-
-NAME
-----
-git-ssh-upload - Push to a remote repository over ssh connection
-
-
-SYNOPSIS
---------
-'git-ssh-upload' [-c] [-t] [-a] [-d] [-v] [-w filename] [--recover] commit-id url
-
-DESCRIPTION
------------
-THIS COMMAND IS DEPRECATED.
-
-Pushes from a remote repository over ssh connection, invoking
-git-ssh-fetch on the other end. It functions identically to
-git-ssh-fetch, aside from which end you run it on.
-
-OPTIONS
--------
-commit-id::
- Id of commit to push.
-
--c::
- Get the commit objects.
--t::
- Get tree associated with the requested commit object.
--a::
- Get all the objects.
--v::
- Report what is uploaded.
--w::
- Writes the commit-id into the filename under [URL]/refs/ on
- the remote end after the transfer is complete.
-
-Author
-------
-Written by Daniel Barkalow <barkalow@iabervon.org>
-
-Documentation
---------------
-Documentation by Daniel Barkalow
-
-GIT
----
-Part of the gitlink:git[7] suite
`filter`
^^^^^^^^
-A `filter` attribute can be set to a string value. This names
+A `filter` attribute can be set to a string value that names a
filter driver specified in the configuration.
-A filter driver consists of `clean` command and `smudge`
+A filter driver consists of a `clean` command and a `smudge`
command, either of which can be left unspecified. Upon
-checkout, when `smudge` command is specified, the command is fed
-the blob object from its standard input, and its standard output
-is used to update the worktree file. Similarly, `clean` command
-is used to convert the contents of worktree file upon checkin.
+checkout, when the `smudge` command is specified, the command is
+fed the blob object from its standard input, and its standard
+output is used to update the worktree file. Similarly, the
+`clean` command is used to convert the contents of worktree file
+upon checkin.
-Missing filter driver definition in the config is not an error
+A missing filter driver definition in the config is not an error
but makes the filter a no-op passthru.
The content filtering is done to massage the content into a
shape that is more convenient for the platform, filesystem, and
-the user to use. The keyword here is "more convenient" and not
+the user to use. The key phrase here is "more convenient" and not
"turning something unusable into usable". In other words, the
intent is that if someone unsets the filter driver definition,
or does not have the appropriate filter program, the project
--- /dev/null
+Date: Fri, 9 Nov 2007 08:28:38 -0800 (PST)
+From: Linus Torvalds <torvalds@linux-foundation.org>
+Subject: corrupt object on git-gc
+Abstract: Some tricks to reconstruct blob objects in order to fix
+ a corrupted repository.
+
+On Fri, 9 Nov 2007, Yossi Leybovich wrote:
+>
+> Did not help still the repository look for this object?
+> Any one know how can I track this object and understand which file is it
+
+So exactly *because* the SHA1 hash is cryptographically secure, the hash
+itself doesn't actually tell you anything, in order to fix a corrupt
+object you basically have to find the "original source" for it.
+
+The easiest way to do that is almost always to have backups, and find the
+same object somewhere else. Backups really are a good idea, and git makes
+it pretty easy (if nothing else, just clone the repository somewhere else,
+and make sure that you do *not* use a hard-linked clone, and preferably
+not the same disk/machine).
+
+But since you don't seem to have backups right now, the good news is that
+especially with a single blob being corrupt, these things *are* somewhat
+debuggable.
+
+First off, move the corrupt object away, and *save* it. The most common
+cause of corruption so far has been memory corruption, but even so, there
+are people who would be interested in seeing the corruption - but it's
+basically impossible to judge the corruption until we can also see the
+original object, so right now the corrupt object is useless, but it's very
+interesting for the future, in the hope that you can re-create a
+non-corrupt version.
+
+So:
+
+> ib]$ mv .git/objects/4b/9458b3786228369c63936db65827de3cc06200 ../
+
+This is the right thing to do, although it's usually best to save it under
+it's full SHA1 name (you just dropped the "4b" from the result ;).
+
+Let's see what that tells us:
+
+> ib]$ git-fsck --full
+> broken link from tree 2d9263c6d23595e7cb2a21e5ebbb53655278dff8
+> to blob 4b9458b3786228369c63936db65827de3cc06200
+> missing blob 4b9458b3786228369c63936db65827de3cc06200
+
+Ok, I removed the "dangling commit" messages, because they are just
+messages about the fact that you probably have rebased etc, so they're not
+at all interesting. But what remains is still very useful. In particular,
+we now know which tree points to it!
+
+Now you can do
+
+ git ls-tree 2d9263c6d23595e7cb2a21e5ebbb53655278dff8
+
+which will show something like
+
+ 100644 blob 8d14531846b95bfa3564b58ccfb7913a034323b8 .gitignore
+ 100644 blob ebf9bf84da0aab5ed944264a5db2a65fe3a3e883 .mailmap
+ 100644 blob ca442d313d86dc67e0a2e5d584b465bd382cbf5c COPYING
+ 100644 blob ee909f2cc49e54f0799a4739d24c4cb9151ae453 CREDITS
+ 040000 tree 0f5f709c17ad89e72bdbbef6ea221c69807009f6 Documentation
+ 100644 blob 1570d248ad9237e4fa6e4d079336b9da62d9ba32 Kbuild
+ 100644 blob 1c7c229a092665b11cd46a25dbd40feeb31661d9 MAINTAINERS
+ ...
+
+and you should now have a line that looks like
+
+ 10064 blob 4b9458b3786228369c63936db65827de3cc06200 my-magic-file
+
+in the output. This already tells you a *lot* it tells you what file the
+corrupt blob came from!
+
+Now, it doesn't tell you quite enough, though: it doesn't tell what
+*version* of the file didn't get correctly written! You might be really
+lucky, and it may be the version that you already have checked out in your
+working tree, in which case fixing this problem is really simple, just do
+
+ git hash-object -w my-magic-file
+
+again, and if it outputs the missing SHA1 (4b945..) you're now all done!
+
+But that's the really lucky case, so let's assume that it was some older
+version that was broken. How do you tell which version it was?
+
+The easiest way to do it is to do
+
+ git log --raw --all --full-history -- subdirectory/my-magic-file
+
+and that will show you the whole log for that file (please realize that
+the tree you had may not be the top-level tree, so you need to figure out
+which subdirectory it was in on your own), and because you're asking for
+raw output, you'll now get something like
+
+ commit abc
+ Author:
+ Date:
+ ..
+ :100644 100644 4b9458b... newsha... M somedirectory/my-magic-file
+
+
+ commit xyz
+ Author:
+ Date:
+
+ ..
+ :100644 100644 oldsha... 4b9458b... M somedirectory/my-magic-file
+
+and this actually tells you what the *previous* and *subsequent* versions
+of that file were! So now you can look at those ("oldsha" and "newsha"
+respectively), and hopefully you have done commits often, and can
+re-create the missing my-magic-file version by looking at those older and
+newer versions!
+
+If you can do that, you can now recreate the missing object with
+
+ git hash-object -w <recreated-file>
+
+and your repository is good again!
+
+(Btw, you could have ignored the fsck, and started with doing a
+
+ git log --raw --all
+
+and just looked for the sha of the missing object (4b9458b..) in that
+whole thing. It's up to you - git does *have* a lot of information, it is
+just missing one particular blob version.
+
+Trying to recreate trees and especially commits is *much* harder. So you
+were lucky that it's a blob. It's quite possible that you can recreate the
+thing.
+
+ Linus
If you run "git branch" at this point, you'll see that git has
temporarily moved you to a new branch named "bisect". This branch
points to a commit (with commit id 65934...) that is reachable from
-v2.6.19 but not from v2.6.18. Compile and test it, and see whether
+"master" but not from v2.6.18. Compile and test it, and see whether
it crashes. Assume it does crash. Then:
-------------------------------------------------
fundamentally different ways to fix the problem:
1. You can create a new commit that undoes whatever was done
- by the previous commit. This is the correct thing if your
+ by the old commit. This is the correct thing if your
mistake has already been made public.
2. You can go back and modify the old commit. You should
-------------------------------------------------
This lists the commits reachable from the previous version of the head.
-This syntax can be used to with any git command that accepts a commit,
+This syntax can be used with any git command that accepts a commit,
not just with git log. Some other examples:
-------------------------------------------------
# Define OLD_ICONV if your library has an old iconv(), where the second
# (input buffer pointer) parameter is declared with type (const char **).
#
+# Define NO_DEFLATE_BOUND if your zlib does not have deflateBound.
+#
# Define NO_R_TO_GCC_LINKER if your gcc does not like "-R/path/lib"
# that tells runtime paths to dynamic libraries;
# "-Wl,-rpath=/path/lib" is used instead.
#
# Define ASCIIDOC8 if you want to format documentation with AsciiDoc 8
#
+# Define DOCBOOK_XSL_172 if you want to format man pages with DocBook XSL v1.72.
+#
# Define NO_PERL_MAKEMAKER if you cannot use Makefiles generated by perl's
# MakeMaker (e.g. using ActiveState under Cygwin).
#
BASIC_CFLAGS += -DOLD_ICONV
endif
+ifdef NO_DEFLATE_BOUND
+ BASIC_CFLAGS += -DNO_DEFLATE_BOUND
+endif
+
ifdef PPC_SHA1
SHA1_HEADER = "ppc/sha1.h"
LIB_OBJS += ppc/sha1.o ppc/sha1ppc.o
$(LIB_OBJS) $(BUILTIN_OBJS): $(LIB_H)
$(patsubst git-%$X,%.o,$(PROGRAMS)): $(LIB_H) $(wildcard */*.h)
+builtin-revert.o builtin-runstatus.o wt-status.o: wt-status.h
$(LIB_FILE): $(LIB_OBJS)
$(QUIET_AR)$(RM) $@ && $(AR) rcs $@ $(LIB_OBJS)
test-delta$X: diff-delta.o patch-delta.o
+test-parse-options$X: parse-options.o
+
.PRECIOUS: $(patsubst test-%$X,test-%.o,$(TEST_PROGRAMS))
test-%$X: test-%.o $(GITLIBS)
### Check documentation
#
check-docs::
- @for v in $(ALL_PROGRAMS) $(BUILT_INS) git$X gitk; \
+ @(for v in $(ALL_PROGRAMS) $(BUILT_INS) git gitk; \
do \
case "$$v" in \
git-merge-octopus | git-merge-ours | git-merge-recursive | \
- git-merge-resolve | git-merge-stupid | \
+ git-merge-resolve | git-merge-stupid | git-merge-subtree | \
git-add--interactive | git-fsck-objects | git-init-db | \
+ git-rebase--interactive | \
git-repo-config | git-fetch--tool ) continue ;; \
esac ; \
test -f "Documentation/$$v.txt" || \
git) ;; \
*) echo "no link: $$v";; \
esac ; \
- done | sort
+ done; \
+ ( \
+ sed -e '1,/^__DATA__/d' \
+ -e 's/[ ].*//' \
+ -e 's/^/listed /' Documentation/cmd-list.perl; \
+ ls -1 Documentation/git*txt | \
+ sed -e 's|Documentation/|documented |' \
+ -e 's/\.txt//'; \
+ ) | while read how cmd; \
+ do \
+ case "$$how,$$cmd" in \
+ *,git-citool | \
+ *,git-gui | \
+ documented,gitattributes | \
+ documented,gitignore | \
+ documented,gitmodules | \
+ documented,git-tools | \
+ sentinel,not,matching,is,ok ) continue ;; \
+ esac; \
+ case " $(ALL_PROGRAMS) $(BUILT_INS) git gitk " in \
+ *" $$cmd "*) ;; \
+ *) echo "removed but $$how: $$cmd" ;; \
+ esac; \
+ done ) | sort
### Make sure built-ins do not have dups and listed in git.c
#
else if (i != argc - 1)
usage(blame_usage); /* garbage at end */
+ setup_work_tree();
if (!has_path_in_work_tree(path))
die("cannot stat path %s: %s",
path, strerror(errno));
* do not default to HEAD, but use the working tree
* or "--contents".
*/
+ setup_work_tree();
sb.final = fake_working_tree_commit(path, contents_from);
add_pending_object(&revs, &(sb.final->object), ":");
}
discard_cache();
read_cache();
refresh_cache(REFRESH_QUIET|REFRESH_UNMERGED);
- if (active_cache_changed) {
- if (write_cache(fd, active_cache, active_nr) ||
- close(fd) ||
- commit_locked_index(lock_file))
- ; /*
- * silently ignore it -- we haven't mucked
- * with the real index.
- */
- }
+
+ if (active_cache_changed &&
+ !write_cache(fd, active_cache, active_nr) && !close(fd))
+ commit_locked_index(lock_file);
+
rollback_lock_file(lock_file);
}
cp++;
if (!*cp)
break;
- np = strchr(cp, '\n');
- if (!np)
- np = cp + strlen(cp);
+ np = strchrnul(cp, '\n');
if (pass) {
lrr_list[i].line = cp;
lrr_list[i].name = cp + 41;
rref++;
if (!*rref)
break;
- next = strchr(rref, '\n');
- if (!next)
- next = rref + strlen(rref);
+ next = strchrnul(rref, '\n');
rreflen = next - rref;
for (i = 0; i < lrr_count; i++) {
#define MAX_IN_VAIN 256
static struct commit_list *rev_list;
-static int non_common_revs, multi_ack, use_thin_pack, use_sideband;
+static int non_common_revs, multi_ack, use_sideband;
static void rev_list_push(struct commit *commit, int mark)
{
(multi_ack ? " multi_ack" : ""),
(use_sideband == 2 ? " side-band-64k" : ""),
(use_sideband == 1 ? " side-band" : ""),
- (use_thin_pack ? " thin-pack" : ""),
+ (args.use_thin_pack ? " thin-pack" : ""),
(args.no_progress ? " no-progress" : ""),
" ofs-delta");
else
}
#define SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3)
+#define REFCOL_WIDTH 10
static int update_local_ref(struct ref *ref,
const char *remote,
if (!hashcmp(ref->old_sha1, ref->new_sha1)) {
if (verbose)
- sprintf(display, "= %-*s %s -> %s", SUMMARY_WIDTH,
- "[up to date]", remote, pretty_ref);
+ sprintf(display, "= %-*s %-*s -> %s", SUMMARY_WIDTH,
+ "[up to date]", REFCOL_WIDTH, remote,
+ pretty_ref);
return 0;
}
* If this is the head, and it's not okay to update
* the head, and the old value of the head isn't empty...
*/
- sprintf(display, "! %-*s %s -> %s (can't fetch in current branch)",
- SUMMARY_WIDTH, "[rejected]", remote, pretty_ref);
+ sprintf(display, "! %-*s %-*s -> %s (can't fetch in current branch)",
+ SUMMARY_WIDTH, "[rejected]", REFCOL_WIDTH, remote,
+ pretty_ref);
return 1;
}
if (!is_null_sha1(ref->old_sha1) &&
!prefixcmp(ref->name, "refs/tags/")) {
- sprintf(display, "- %-*s %s -> %s",
- SUMMARY_WIDTH, "[tag update]", remote, pretty_ref);
+ sprintf(display, "- %-*s %-*s -> %s",
+ SUMMARY_WIDTH, "[tag update]", REFCOL_WIDTH, remote,
+ pretty_ref);
return s_update_ref("updating tag", ref, 0);
}
what = "[new branch]";
}
- sprintf(display, "* %-*s %s -> %s",
- SUMMARY_WIDTH, what, remote, pretty_ref);
+ sprintf(display, "* %-*s %-*s -> %s", SUMMARY_WIDTH, what,
+ REFCOL_WIDTH, remote, pretty_ref);
return s_update_ref(msg, ref, 0);
}
strcpy(quickref, find_unique_abbrev(current->object.sha1, DEFAULT_ABBREV));
strcat(quickref, "..");
strcat(quickref, find_unique_abbrev(ref->new_sha1, DEFAULT_ABBREV));
- sprintf(display, " %-*s %s -> %s (fast forward)",
- SUMMARY_WIDTH, quickref, remote, pretty_ref);
+ sprintf(display, " %-*s %-*s -> %s", SUMMARY_WIDTH, quickref,
+ REFCOL_WIDTH, remote, pretty_ref);
return s_update_ref("fast forward", ref, 1);
} else if (force || ref->force) {
char quickref[84];
strcpy(quickref, find_unique_abbrev(current->object.sha1, DEFAULT_ABBREV));
strcat(quickref, "...");
strcat(quickref, find_unique_abbrev(ref->new_sha1, DEFAULT_ABBREV));
- sprintf(display, "+ %-*s %s -> %s (forced update)",
- SUMMARY_WIDTH, quickref, remote, pretty_ref);
+ sprintf(display, "+ %-*s %-*s -> %s (forced update)",
+ SUMMARY_WIDTH, quickref, REFCOL_WIDTH, remote, pretty_ref);
return s_update_ref("forced-update", ref, 1);
} else {
- sprintf(display, "! %-*s %s -> %s (non fast forward)",
- SUMMARY_WIDTH, "[rejected]", remote, pretty_ref);
+ sprintf(display, "! %-*s %-*s -> %s (non fast forward)",
+ SUMMARY_WIDTH, "[rejected]", REFCOL_WIDTH, remote,
+ pretty_ref);
return 1;
}
}
if (!eol)
return "";
eol++;
- if (eol[1] == '\n')
+ if (*eol == '\n')
return ""; /* end of header */
buf = eol;
}
OPT_GROUP(""),
OPT_INTEGER( 0 , "count", &maxcount, "show only <n> matched refs"),
OPT_STRING( 0 , "format", &format, "format", "format to use for the output"),
- OPT_CALLBACK(0 , "sort", &sort_tail, "key",
+ OPT_CALLBACK(0 , "sort", sort_tail, "key",
"field name to sort on", &opt_parse_sort),
OPT_END(),
};
static char *extra_headers = NULL;
static int extra_headers_size = 0;
static const char *fmt_patch_suffix = ".patch";
+static int numbered = 0;
+static int auto_number = 0;
static int git_format_config(const char *var, const char *value)
{
if (!strcmp(var, "diff.color") || !strcmp(var, "color.diff")) {
return 0;
}
+ if (!strcmp(var, "format.numbered")) {
+ if (!strcasecmp(value, "auto")) {
+ auto_number = 1;
+ return 0;
+ }
+
+ numbered = git_config_bool(var, value);
+ return 0;
+ }
return git_log_config(var, value);
}
struct rev_info rev;
int nr = 0, total, i, j;
int use_stdout = 0;
- int numbered = 0;
int start_number = -1;
int keep_subject = 0;
int numbered_files = 0; /* _just_ numbers */
else if (!strcmp(argv[i], "-n") ||
!strcmp(argv[i], "--numbered"))
numbered = 1;
+ else if (!strcmp(argv[i], "-N") ||
+ !strcmp(argv[i], "--no-numbered")) {
+ numbered = 0;
+ auto_number = 0;
+ }
else if (!prefixcmp(argv[i], "--start-number="))
start_number = strtol(argv[i] + 15, NULL, 10);
else if (!strcmp(argv[i], "--numbered-files"))
list[nr - 1] = commit;
}
total = nr;
+ if (!keep_subject && auto_number && total > 1)
+ numbered = 1;
if (numbered)
rev.total = total + start_number - 1;
rev.add_signoff = add_signoff;
break;
}
- if (require_work_tree && !is_inside_work_tree()) {
- const char *work_tree = get_git_work_tree();
- if (!work_tree || chdir(work_tree))
- die("This operation must be run in a work tree");
- }
+ if (require_work_tree && !is_inside_work_tree())
+ setup_work_tree();
pathspec = get_pathspec(prefix, argv + i);
{
DIR *dir;
struct dirent *dent;
+ char name[PATH_MAX];
+ char *subs[] = { "cur", "new", NULL };
+ char **sub;
+
+ for (sub = subs; *sub; ++sub) {
+ snprintf(name, sizeof(name), "%s/%s", path, *sub);
+ if ((dir = opendir(name)) == NULL) {
+ if (errno == ENOENT)
+ continue;
+ error("cannot opendir %s (%s)", name, strerror(errno));
+ return -1;
+ }
- if ((dir = opendir(path)) == NULL) {
- error("cannot opendir %s (%s)", path, strerror(errno));
- return -1;
- }
+ while ((dent = readdir(dir)) != NULL) {
+ if (dent->d_name[0] == '.')
+ continue;
+ snprintf(name, sizeof(name), "%s/%s", *sub, dent->d_name);
+ path_list_insert(name, list);
+ }
- while ((dent = readdir(dir)) != NULL) {
- if (dent->d_name[0] == '.')
- continue;
- path_list_insert(dent->d_name, list);
+ closedir(dir);
}
- closedir(dir);
-
return 0;
}
int nr_prec, int skip)
{
char file[PATH_MAX];
- char curdir[PATH_MAX];
char name[PATH_MAX];
int ret = -1;
int i;
struct path_list list = {NULL, 0, 0, 1};
- snprintf(curdir, sizeof(curdir), "%s/cur", maildir);
- if (populate_maildir_list(&list, curdir) < 0)
+ if (populate_maildir_list(&list, maildir) < 0)
goto out;
for (i = 0; i < list.nr; i++) {
FILE *f;
- snprintf(file, sizeof(file), "%s/%s", curdir, list.items[i].path);
+ snprintf(file, sizeof(file), "%s/%s", maildir, list.items[i].path);
f = fopen(file, "r");
if (!f) {
error("cannot open mail %s (%s)", file, strerror(errno));
fclose(f);
}
- path_list_clear(&list, 1);
-
ret = skip;
out:
+ path_list_clear(&list, 1);
return ret;
}
flags |= TRANSPORT_PUSH_FORCE;
if (dry_run)
flags |= TRANSPORT_PUSH_DRY_RUN;
+ if (verbose)
+ flags |= TRANSPORT_PUSH_VERBOSE;
if (tags)
add_refspec("refs/tags/*");
if (all)
#include "tree.h"
static const char builtin_reset_usage[] =
-"git-reset [--mixed | --soft | --hard] [<commit-ish>] [ [--] <paths>...]";
+"git-reset [--mixed | --soft | --hard] [-q] [<commit-ish>] [ [--] <paths>...]";
static char *args_to_str(const char **argv)
{
static int unmerged_files(void)
{
- char b;
- ssize_t len;
- struct child_process cmd;
- const char *argv_ls_files[] = {"ls-files", "--unmerged", NULL};
-
- memset(&cmd, 0, sizeof(cmd));
- cmd.argv = argv_ls_files;
- cmd.git_cmd = 1;
- cmd.out = -1;
-
- if (start_command(&cmd))
- die("Could not run sub-command: git ls-files");
-
- len = xread(cmd.out, &b, 1);
- if (len < 0)
- die("Could not read output from git ls-files: %s",
- strerror(errno));
- finish_command(&cmd);
-
- return len;
+ int i;
+ read_cache();
+ for (i = 0; i < active_nr; i++) {
+ struct cache_entry *ce = active_cache[i];
+ if (ce_stage(ce))
+ return 1;
+ }
+ return 0;
}
static int reset_index_file(const unsigned char *sha1, int is_hard_reset)
printf("\n");
}
-static int update_index_refresh(void)
+static int update_index_refresh(int fd, struct lock_file *index_lock)
{
- const char *argv_update_index[] = {"update-index", "--refresh", NULL};
- return run_command_v_opt(argv_update_index, RUN_GIT_CMD);
-}
+ int result;
-struct update_cb_data {
- int index_fd;
- struct lock_file *lock;
- int exit_code;
-};
+ if (!index_lock) {
+ index_lock = xcalloc(1, sizeof(struct lock_file));
+ fd = hold_locked_index(index_lock, 1);
+ }
+
+ if (read_cache() < 0)
+ return error("Could not read index");
+ result = refresh_cache(0) ? 1 : 0;
+ if (write_cache(fd, active_cache, active_nr) ||
+ close(fd) ||
+ commit_locked_index(index_lock))
+ return error ("Could not refresh index");
+ return result;
+}
static void update_index_from_diff(struct diff_queue_struct *q,
struct diff_options *opt, void *data)
{
int i;
- struct update_cb_data *cb = data;
+ int *discard_flag = data;
/* do_diff_cache() mangled the index */
discard_cache();
+ *discard_flag = 1;
read_cache();
for (i = 0; i < q->nr; i++) {
} else
remove_file_from_cache(one->path);
}
-
- cb->exit_code = write_cache(cb->index_fd, active_cache, active_nr) ||
- close(cb->index_fd) ||
- commit_locked_index(cb->lock);
}
static int read_from_tree(const char *prefix, const char **argv,
unsigned char *tree_sha1)
{
+ struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
+ int index_fd, index_was_discarded = 0;
struct diff_options opt;
- struct update_cb_data cb;
memset(&opt, 0, sizeof(opt));
diff_tree_setup_paths(get_pathspec(prefix, (const char **)argv), &opt);
opt.output_format = DIFF_FORMAT_CALLBACK;
opt.format_callback = update_index_from_diff;
- opt.format_callback_data = &cb;
+ opt.format_callback_data = &index_was_discarded;
- cb.lock = xcalloc(1, sizeof(struct lock_file));
- cb.index_fd = hold_locked_index(cb.lock, 1);
- cb.exit_code = 0;
+ index_fd = hold_locked_index(lock, 1);
+ index_was_discarded = 0;
read_cache();
if (do_diff_cache(tree_sha1, &opt))
return 1;
diffcore_std(&opt);
diff_flush(&opt);
- return cb.exit_code;
+ if (!index_was_discarded)
+ /* The index is still clobbered from do_diff_cache() */
+ discard_cache();
+ return update_index_refresh(index_fd, lock);
}
static void prepend_reflog_action(const char *action, char *buf, size_t size)
int cmd_reset(int argc, const char **argv, const char *prefix)
{
- int i = 1, reset_type = NONE, update_ref_status = 0;
+ int i = 1, reset_type = NONE, update_ref_status = 0, quiet = 0;
const char *rev = "HEAD";
unsigned char sha1[20], *orig = NULL, sha1_orig[20],
*old_orig = NULL, sha1_old_orig[20];
reflog_action = args_to_str(argv);
setenv("GIT_REFLOG_ACTION", reflog_action, 0);
- if (i < argc) {
+ while (i < argc) {
if (!strcmp(argv[i], "--mixed")) {
reset_type = MIXED;
i++;
reset_type = HARD;
i++;
}
+ else if (!strcmp(argv[i], "-q")) {
+ quiet = 1;
+ i++;
+ }
+ else
+ break;
}
if (i < argc && argv[i][0] != '-')
else if (reset_type != NONE)
die("Cannot do %s reset with paths.",
reset_type_names[reset_type]);
- if (read_from_tree(prefix, argv + i, sha1))
- return 1;
- return update_index_refresh() ? 1 : 0;
+ return read_from_tree(prefix, argv + i, sha1);
}
if (reset_type == NONE)
reset_type = MIXED; /* by default */
switch (reset_type) {
case HARD:
- if (!update_ref_status)
+ if (!update_ref_status && !quiet)
print_new_head_line(commit);
break;
case SOFT: /* Nothing else to do. */
break;
case MIXED: /* Report what has not been updated. */
- update_index_refresh();
+ update_index_refresh(0, NULL);
break;
}
if (no_commit) {
/*
* We do not intend to commit immediately. We just want to
- * merge the differences in.
+ * merge the differences in, so let's compute the tree
+ * that represents the "current" state for merge-recursive
+ * to work on.
*/
if (write_tree(head, 0, NULL))
die ("Your index file is unmerged.");
if (get_sha1("HEAD", head))
die ("You do not have a valid HEAD");
wt_status_prepare(&s);
- if (s.commitable || s.workdir_dirty)
+ if (s.commitable)
die ("Dirty index: cannot %s", me);
discard_cache();
}
if (!argc)
usage_with_options(builtin_rm_usage, builtin_rm_options);
+ if (!index_only)
+ setup_work_tree();
+
pathspec = get_pathspec(prefix, argv);
seen = NULL;
for (i = 0; pathspec[i] ; i++)
}
printf("%-15s ", refname);
- sp = buf = read_sha1_file(sha1, &type, &size);
- if (!buf)
+ buf = read_sha1_file(sha1, &type, &size);
+ if (!buf || !size)
return 0;
- if (!size) {
+
+ /* skip header */
+ sp = strstr(buf, "\n\n");
+ if (!sp) {
free(buf);
return 0;
}
- /* skip header */
- while (sp + 1 < buf + size &&
- !(sp[0] == '\n' && sp[1] == '\n'))
- sp++;
/* only take up to "lines" lines, and strip the signature */
for (i = 0, sp += 2;
i < filter->lines && sp < buf + size &&
}
/* returns an fd */
-int read_bundle_header(const char *path, struct bundle_header *header) {
+int read_bundle_header(const char *path, struct bundle_header *header)
+{
char buffer[1024];
int fd;
long fpos;
#include SHA1_HEADER
#include <zlib.h>
-#if ZLIB_VERNUM < 0x1200
+#if defined(NO_DEFLATE_BOUND) || ZLIB_VERNUM < 0x1200
#define deflateBound(c,s) ((s) + (((s) + 7) >> 3) + (((s) + 63) >> 6) + 11)
#endif
#define ALTERNATE_DB_ENVIRONMENT "GIT_ALTERNATE_OBJECT_DIRECTORIES"
extern const char **get_pathspec(const char *prefix, const char **pathspec);
+extern void setup_work_tree(void);
extern const char *setup_git_directory_gently(int *);
extern const char *setup_git_directory(void);
extern const char *prefix_path(const char *prefix, int len, const char *path);
NO_STRLCPY=@NO_STRLCPY@
NO_SETENV=@NO_SETENV@
NO_ICONV=@NO_ICONV@
+NO_DEFLATE_BOUND=@NO_DEFLATE_BOUND@
AC_ARG_WITH([lib],
[AS_HELP_STRING([--with-lib=ARG],
[ARG specifies alternative name for lib directory])],
- [if test "$withval" = "no" -o "$withval" = "yes"; then \
+ [if test "$withval" = "no" || test "$withval" = "yes"; then \
AC_MSG_WARN([You should provide name for --with-lib=ARG]); \
else \
GIT_CONF_APPEND_LINE(lib=$withval); \
AC_SUBST(NO_ICONV)
test -n "$NEEDS_LIBICONV" && LIBS="$LIBS -liconv"
#
+# Define NO_DEFLATE_BOUND if deflateBound is missing from zlib.
+AC_DEFUN([ZLIBTEST_SRC], [
+#include <zlib.h>
+
+int main(void)
+{
+ deflateBound(0, 0);
+ return 0;
+}
+])
+AC_MSG_CHECKING([for deflateBound in -lz])
+old_LIBS="$LIBS"
+LIBS="$LIBS -lz"
+AC_LINK_IFELSE(ZLIBTEST_SRC,
+ [AC_MSG_RESULT([yes])],
+ [AC_MSG_RESULT([no])
+ NO_DEFLATE_BOUND=yes])
+LIBS="$old_LIBS"
+AC_SUBST(NO_DEFLATE_BOUND)
+#
# Define NEEDS_SOCKET if linking with libc is not enough (SunOS,
# Patrick Mauritz).
AC_CHECK_LIB([c], [socket],
[AC_LANG_PROGRAM([AC_INCLUDES_DEFAULT],
[[char buf[64];
if (sprintf(buf, "%lld%hhd%jd%zd%td", (long long int)1, (char)2, (intmax_t)3, (size_t)4, (ptrdiff_t)5) != 5)
- exit(1);
+ return 1;
else if (strcmp(buf, "12345"))
- exit(2);]])],
+ return 2;]])],
[ac_cv_c_c99_format=yes],
[ac_cv_c_c99_format=no])
])
a plus sign, it is also executable"""
return (re.search(r"(^[cku]?x)|\+.*x", kind) != None)
+def setP4ExecBit(file, mode):
+ # Reopens an already open file and changes the execute bit to match
+ # the execute bit setting in the passed in mode.
+
+ p4Type = "+x"
+
+ if not isModeExec(mode):
+ p4Type = getP4OpenedType(file)
+ p4Type = re.sub('^([cku]?)x(.*)', '\\1\\2', p4Type)
+ p4Type = re.sub('(.*?\+.*?)x(.*?)', '\\1\\2', p4Type)
+ if p4Type[-1] == "+":
+ p4Type = p4Type[0:-1]
+
+ system("p4 reopen -t %s %s" % (p4Type, file))
+
+def getP4OpenedType(file):
+ # Returns the perforce file type for the given file.
+
+ result = read_pipe("p4 opened %s" % file)
+ match = re.match(".*\((.+)\)$", result)
+ if match:
+ return match.group(1)
+ else:
+ die("Could not determine file type for %s" % file)
+
+def diffTreePattern():
+ # This is a simple generator for the diff tree regex pattern. This could be
+ # a class variable if this and parseDiffTreeEntry were a part of a class.
+ pattern = re.compile(':(\d+) (\d+) (\w+) (\w+) ([A-Z])(\d+)?\t(.*?)((\t(.*))|$)')
+ while True:
+ yield pattern
+
+def parseDiffTreeEntry(entry):
+ """Parses a single diff tree entry into its component elements.
+
+ See git-diff-tree(1) manpage for details about the format of the diff
+ output. This method returns a dictionary with the following elements:
+
+ src_mode - The mode of the source file
+ dst_mode - The mode of the destination file
+ src_sha1 - The sha1 for the source file
+ dst_sha1 - The sha1 fr the destination file
+ status - The one letter status of the diff (i.e. 'A', 'M', 'D', etc)
+ status_score - The score for the status (applicable for 'C' and 'R'
+ statuses). This is None if there is no score.
+ src - The path for the source file.
+ dst - The path for the destination file. This is only present for
+ copy or renames. If it is not present, this is None.
+
+ If the pattern is not matched, None is returned."""
+
+ match = diffTreePattern().next().match(entry)
+ if match:
+ return {
+ 'src_mode': match.group(1),
+ 'dst_mode': match.group(2),
+ 'src_sha1': match.group(3),
+ 'dst_sha1': match.group(4),
+ 'status': match.group(5),
+ 'status_score': match.group(6),
+ 'src': match.group(7),
+ 'dst': match.group(10)
+ }
+ return None
+
+def isModeExec(mode):
+ # Returns True if the given git mode represents an executable file,
+ # otherwise False.
+ return mode[-3:] == "755"
+
+def isModeExecChanged(src_mode, dst_mode):
+ return isModeExec(src_mode) != isModeExec(dst_mode)
+
def p4CmdList(cmd, stdin=None, stdin_mode='w+b'):
cmd = "p4 -G %s" % cmd
if verbose:
else:
print "Applying %s" % (read_pipe("git log --max-count=1 --pretty=oneline %s" % id))
diffOpts = ("", "-M")[self.detectRename]
- diff = read_pipe_lines("git diff-tree -r --name-status %s \"%s^\" \"%s\"" % (diffOpts, id, id))
+ diff = read_pipe_lines("git diff-tree -r %s \"%s^\" \"%s\"" % (diffOpts, id, id))
filesToAdd = set()
filesToDelete = set()
editedFiles = set()
+ filesToChangeExecBit = {}
for line in diff:
- modifier = line[0]
- path = line[1:].strip()
+ diff = parseDiffTreeEntry(line)
+ modifier = diff['status']
+ path = diff['src']
if modifier == "M":
system("p4 edit \"%s\"" % path)
+ if isModeExecChanged(diff['src_mode'], diff['dst_mode']):
+ filesToChangeExecBit[path] = diff['dst_mode']
editedFiles.add(path)
elif modifier == "A":
filesToAdd.add(path)
+ filesToChangeExecBit[path] = diff['dst_mode']
if path in filesToDelete:
filesToDelete.remove(path)
elif modifier == "D":
if path in filesToAdd:
filesToAdd.remove(path)
elif modifier == "R":
- src, dest = line.strip().split("\t")[1:3]
+ src, dest = diff['src'], diff['dst']
system("p4 integrate -Dt \"%s\" \"%s\"" % (src, dest))
system("p4 edit \"%s\"" % (dest))
+ if isModeExecChanged(diff['src_mode'], diff['dst_mode']):
+ filesToChangeExecBit[dest] = diff['dst_mode']
os.unlink(dest)
editedFiles.add(dest)
filesToDelete.add(src)
system("p4 revert \"%s\"" % f)
system("p4 delete \"%s\"" % f)
+ # Set/clear executable bits
+ for f in filesToChangeExecBit.keys():
+ mode = filesToChangeExecBit[f]
+ setP4ExecBit(f, mode)
+
logMessage = ""
if not self.directSubmit:
logMessage = extractLogMessageFromGitCommit(id)
#
# Copyright (c) 2007 Andy Parkins
#
-# An example hook script to mail out commit update information. This hook sends emails
-# listing new revisions to the repository introduced by the change being reported. The
-# rule is that (for branch updates) each commit will appear on one email and one email
-# only.
+# An example hook script to mail out commit update information. This hook
+# sends emails listing new revisions to the repository introduced by the
+# change being reported. The rule is that (for branch updates) each commit
+# will appear on one email and one email only.
#
-# This hook is stored in the contrib/hooks directory. Your distribution will have put
-# this somewhere standard. You should make this script executable then link to it in
-# the repository you would like to use it in. For example, on debian the hook is stored
-# in /usr/share/doc/git-core/contrib/hooks/post-receive-email:
+# This hook is stored in the contrib/hooks directory. Your distribution
+# will have put this somewhere standard. You should make this script
+# executable then link to it in the repository you would like to use it in.
+# For example, on debian the hook is stored in
+# /usr/share/doc/git-core/contrib/hooks/post-receive-email:
#
# chmod a+x post-receive-email
# cd /path/to/your/repository.git
# ln -sf /usr/share/doc/git-core/contrib/hooks/post-receive-email hooks/post-receive
#
-# This hook script assumes it is enabled on the central repository of a project, with
-# all users pushing only to it and not between each other. It will still work if you
-# don't operate in that style, but it would become possible for the email to be from
-# someone other than the person doing the push.
+# This hook script assumes it is enabled on the central repository of a
+# project, with all users pushing only to it and not between each other. It
+# will still work if you don't operate in that style, but it would become
+# possible for the email to be from someone other than the person doing the
+# push.
#
# Config
# ------
# emails for every ref update.
# hooks.announcelist
# This is the list that all pushes of annotated tags will go to. Leave it
-# blank to default to the mailinglist field. The announce emails lists the
-# short log summary of the changes since the last annotated tag.
-# hook.envelopesender
-# If set then the -f option is passed to sendmail to allow the envelope sender
-# address to be set
+# blank to default to the mailinglist field. The announce emails lists
+# the short log summary of the changes since the last annotated tag.
+# hooks.envelopesender
+# If set then the -f option is passed to sendmail to allow the envelope
+# sender address to be set
+# hooks.emailprefix
+# All emails have their subjects prefixed with this prefix, or "[SCM]"
+# if emailprefix is unset, to aid filtering
#
# Notes
# -----
-# All emails have their subjects prefixed with "[SCM]" to aid filtering.
# All emails include the headers "X-Git-Refname", "X-Git-Oldrev",
# "X-Git-Newrev", and "X-Git-Reftype" to enable fine tuned filtering and
# give information for debugging.
# this is and calls the appropriate body-generation routine after outputting
# the common header
#
-# Note this function doesn't actually generate any email output, that is taken
-# care of by the functions it calls:
+# Note this function doesn't actually generate any email output, that is
+# taken care of by the functions it calls:
# - generate_email_header
# - generate_create_XXXX_email
# - generate_update_XXXX_email
fi
# Email parameters
- # The committer will be obtained from the latest existing rev; so
- # for a deletion it will be the oldrev, for the others, then newrev
- committer=$(git show --pretty=full -s $rev | sed -ne "s/^Commit: //p" |
- sed -ne 's/\(.*\) </"\1" </p')
# The email subject will contain the best description of the ref
# that we can build from the parameters
describe=$(git describe $rev 2>/dev/null)
# Generate header
cat <<-EOF
To: $recipients
- Subject: ${EMAILPREFIX}$projectdesc $refname_type, $short_refname, ${change_type}d. $describe
+ Subject: ${emailprefix}$projectdesc $refname_type, $short_refname, ${change_type}d. $describe
X-Git-Refname: $refname
X-Git-Reftype: $refname_type
X-Git-Oldrev: $oldrev
echo $LOGBEGIN
# This shows all log entries that are not already covered by
# another ref - i.e. commits that are now accessible from this
- # ref that were previously not accessible (see generate_update_branch_email
- # for the explanation of this command)
+ # ref that were previously not accessible
+ # (see generate_update_branch_email for the explanation of this
+ # command)
git rev-parse --not --branches | grep -v $(git rev-parse $refname) |
git rev-list --pretty --stdin $newrev
echo $LOGEND
#
# git-rev-list N ^O ^X ^N
#
- # So, we need to build up the list more carefully. git-rev-parse will
- # generate a list of revs that may be fed into git-rev-list. We can get
- # it to make the "--not --all" part and then filter out the "^N" with:
+ # So, we need to build up the list more carefully. git-rev-parse
+ # will generate a list of revs that may be fed into git-rev-list.
+ # We can get it to make the "--not --all" part and then filter out
+ # the "^N" with:
#
# git-rev-parse --not --all | grep -v N
#
# git-rev-list N ^O ^X
#
# This leaves a problem when someone else updates the repository
- # while this script is running. Their new value of the ref we're working
- # on would be included in the "--not --all" output; and as our $newrev
- # would be an ancestor of that commit, it would exclude all of our
- # commits. What we really want is to exclude the current value of
- # $refname from the --not list, rather than N itself. So:
+ # while this script is running. Their new value of the ref we're
+ # working on would be included in the "--not --all" output; and as
+ # our $newrev would be an ancestor of that commit, it would exclude
+ # all of our commits. What we really want is to exclude the current
+ # value of $refname from the --not list, rather than N itself. So:
#
# git-rev-parse --not --all | grep -v $(git-rev-parse $refname)
#
- # Get's us to something pretty safe (apart from the small time between
- # refname being read, and git-rev-parse running - for that, I give up)
+ # Get's us to something pretty safe (apart from the small time
+ # between refname being read, and git-rev-parse running - for that,
+ # I give up)
#
#
# Next problem, consider this:
# \
# * --- X --- * --- N ($newrev)
#
- # That is to say, there is no guarantee that oldrev is a strict subset of
- # newrev (it would have required a --force, but that's allowed). So, we
- # can't simply say rev-list $oldrev..$newrev. Instead we find the common
- # base of the two revs and list from there.
+ # That is to say, there is no guarantee that oldrev is a strict
+ # subset of newrev (it would have required a --force, but that's
+ # allowed). So, we can't simply say rev-list $oldrev..$newrev.
+ # Instead we find the common base of the two revs and list from
+ # there.
#
- # As above, we need to take into account the presence of X; if another
- # branch is already in the repository and points at some of the revisions
- # that we are about to output - we don't want them. The solution is as
- # before: git-rev-parse output filtered.
+ # As above, we need to take into account the presence of X; if
+ # another branch is already in the repository and points at some of
+ # the revisions that we are about to output - we don't want them.
+ # The solution is as before: git-rev-parse output filtered.
#
- # Finally, tags:
- # 1 --- 2 --- O --- T --- 3 --- 4 --- N
+ # Finally, tags: 1 --- 2 --- O --- T --- 3 --- 4 --- N
#
# Tags pushed into the repository generate nice shortlog emails that
# summarise the commits between them and the previous tag. However,
# for a branch update. Therefore we still want to output revisions
# that have been output on a tag email.
#
- # Luckily, git-rev-parse includes just the tool. Instead of using "--all"
- # we use "--branches"; this has the added benefit that "remotes/" will
- # be ignored as well.
-
- # List all of the revisions that were removed by this update, in a fast forward
- # update, this list will be empty, because rev-list O ^N is empty. For a non
- # fast forward, O ^N is the list of removed revisions
+ # Luckily, git-rev-parse includes just the tool. Instead of using
+ # "--all" we use "--branches"; this has the added benefit that
+ # "remotes/" will be ignored as well.
+
+ # List all of the revisions that were removed by this update, in a
+ # fast forward update, this list will be empty, because rev-list O
+ # ^N is empty. For a non fast forward, O ^N is the list of removed
+ # revisions
fast_forward=""
rev=""
for rev in $(git rev-list $newrev..$oldrev)
fi
# List all the revisions from baserev to newrev in a kind of
- # "table-of-contents"; note this list can include revisions that have
- # already had notification emails and is present to show the full detail
- # of the change from rolling back the old revision to the base revision and
- # then forward to the new revision
+ # "table-of-contents"; note this list can include revisions that
+ # have already had notification emails and is present to show the
+ # full detail of the change from rolling back the old revision to
+ # the base revision and then forward to the new revision
for rev in $(git rev-list $oldrev..$newrev)
do
revtype=$(git cat-file -t "$rev")
if [ "$fast_forward" ]; then
echo " from $oldrev ($oldrev_type)"
else
- # 1. Existing revisions were removed. In this case newrev is a
- # subset of oldrev - this is the reverse of a fast-forward,
- # a rewind
- # 2. New revisions were added on top of an old revision, this is
- # a rewind and addition.
+ # 1. Existing revisions were removed. In this case newrev
+ # is a subset of oldrev - this is the reverse of a
+ # fast-forward, a rewind
+ # 2. New revisions were added on top of an old revision,
+ # this is a rewind and addition.
- # (1) certainly happened, (2) possibly. When (2) hasn't happened,
- # we set a flag to indicate that no log printout is required.
+ # (1) certainly happened, (2) possibly. When (2) hasn't
+ # happened, we set a flag to indicate that no log printout
+ # is required.
echo ""
- # Find the common ancestor of the old and new revisions and compare
- # it with newrev
+ # Find the common ancestor of the old and new revisions and
+ # compare it with newrev
baserev=$(git merge-base $oldrev $newrev)
rewind_only=""
if [ "$baserev" = "$newrev" ]; then
git rev-parse --not --branches | grep -v $(git rev-parse $refname) |
git rev-list --pretty --stdin $oldrev..$newrev
- # XXX: Need a way of detecting whether git rev-list actually outputted
- # anything, so that we can issue a "no new revisions added by this
- # update" message
+ # XXX: Need a way of detecting whether git rev-list actually
+ # outputted anything, so that we can issue a "no new
+ # revisions added by this update" message
echo $LOGEND
else
echo "No new revisions were added by this update."
fi
- # The diffstat is shown from the old revision to the new revision. This
- # is to show the truth of what happened in this change. There's no point
- # showing the stat from the base to the new revision because the base
- # is effectively a random revision at this point - the user will be
- # interested in what this revision changed - including the undoing of
- # previous revisions in the case of non-fast forward updates.
+ # The diffstat is shown from the old revision to the new revision.
+ # This is to show the truth of what happened in this change.
+ # There's no point showing the stat from the base to the new
+ # revision because the base is effectively a random revision at this
+ # point - the user will be interested in what this revision changed
+ # - including the undoing of previous revisions in the case of
+ # non-fast forward updates.
echo ""
echo "Summary of changes:"
git diff-tree --stat --summary --find-copies-harder $oldrev..$newrev
#
generate_atag_email()
{
- # Use git-for-each-ref to pull out the individual fields from the tag
+ # Use git-for-each-ref to pull out the individual fields from the
+ # tag
eval $(git for-each-ref --shell --format='
tagobject=%(*objectname)
tagtype=%(*objecttype)
echo " tagging $tagobject ($tagtype)"
case "$tagtype" in
commit)
+
# If the tagged object is a commit, then we assume this is a
- # release, and so we calculate which tag this tag is replacing
+ # release, and so we calculate which tag this tag is
+ # replacing
prevtag=$(git describe --abbrev=0 $newrev^ 2>/dev/null)
if [ -n "$prevtag" ]; then
echo ""
echo $LOGBEGIN
- # Show the content of the tag message; this might contain a change log
- # or release notes so is worth displaying.
+ # Show the content of the tag message; this might contain a change
+ # log or release notes so is worth displaying.
git cat-file tag $newrev | sed -e '1,/^$/d'
echo ""
case "$tagtype" in
commit)
- # Only commit tags make sense to have rev-list operations performed
- # on them
+ # Only commit tags make sense to have rev-list operations
+ # performed on them
if [ -n "$prevtag" ]; then
# Show changes since the previous release
git rev-list --pretty=short "$prevtag..$newrev" | git shortlog
else
- # No previous tag, show all the changes since time began
+ # No previous tag, show all the changes since time
+ # began
git rev-list --pretty=short $newrev | git shortlog
fi
;;
*)
- # XXX: Is there anything useful we can do for non-commit objects?
+ # XXX: Is there anything useful we can do for non-commit
+ # objects?
;;
esac
#
generate_general_email()
{
- # Unannotated tags are more about marking a point than releasing a version;
- # therefore we don't do the shortlog summary that we do for annotated tags
- # above - we simply show that the point has been marked, and print the log
- # message for the marked point for reference purposes
+ # Unannotated tags are more about marking a point than releasing a
+ # version; therefore we don't do the shortlog summary that we do for
+ # annotated tags above - we simply show that the point has been
+ # marked, and print the log message for the marked point for
+ # reference purposes
#
- # Note this section also catches any other reference type (although there
- # aren't any) and deals with them in the same way.
+ # Note this section also catches any other reference type (although
+ # there aren't any) and deals with them in the same way.
echo ""
if [ "$newrev_type" = "commit" ]; then
git show --no-color --root -s $newrev
echo $LOGEND
else
- # What can we do here? The tag marks an object that is not a commit,
- # so there is no log for us to display. It's probably not wise to
- # output git-cat-file as it could be a binary blob. We'll just say how
- # big it is
+ # What can we do here? The tag marks an object that is not
+ # a commit, so there is no log for us to display. It's
+ # probably not wise to output git-cat-file as it could be a
+ # binary blob. We'll just say how big it is
echo "$newrev is a $newrev_type, and is $(git cat-file -s $newrev) bytes long."
fi
}
# ---------------------------- main()
# --- Constants
-EMAILPREFIX="[SCM] "
LOGBEGIN="- Log -----------------------------------------------------------------"
LOGEND="-----------------------------------------------------------------------"
fi
projectdesc=$(sed -ne '1p' "$GIT_DIR/description")
-# Check if the description is unchanged from it's default, and shorten it to a
-# more manageable length if it is
+# Check if the description is unchanged from it's default, and shorten it to
+# a more manageable length if it is
if expr "$projectdesc" : "Unnamed repository.*$" >/dev/null
then
projectdesc="UNNAMED PROJECT"
recipients=$(git repo-config hooks.mailinglist)
announcerecipients=$(git repo-config hooks.announcelist)
envelopesender=$(git-repo-config hooks.envelopesender)
+emailprefix=$(git-repo-config hooks.emailprefix || echo '[SCM] ')
# --- Main loop
-# Allow dual mode: run from the command line just like the update hook, or if
-# no arguments are given then run as a hook script
+# Allow dual mode: run from the command line just like the update hook, or
+# if no arguments are given then run as a hook script
if [ -n "$1" -a -n "$2" -a -n "$3" ]; then
# Output to the terminal in command line mode - if someone wanted to
- # resend an email; they could redirect the output to sendmail themselves
+ # resend an email; they could redirect the output to sendmail
+ # themselves
PAGER= generate_email $2 $3 $1
else
while read oldrev newrev refname
for (;;) {
int ret = xwrite(f->fd, buf, count);
if (ret > 0) {
- display_throughput(f->tp, ret);
+ f->total += ret;
+ display_throughput(f->tp, f->total);
buf = (char *) buf + ret;
count -= ret;
if (count)
struct sha1file *sha1fd_throughput(int fd, const char *name, struct progress *tp)
{
- struct sha1file *f;
- unsigned len;
-
- f = xmalloc(sizeof(*f));
-
- len = strlen(name);
- if (len >= PATH_MAX)
- die("you wascally wabbit, you");
- f->namelen = len;
- memcpy(f->name, name, len+1);
-
+ struct sha1file *f = xmalloc(sizeof(*f));
f->fd = fd;
- f->error = 0;
f->offset = 0;
+ f->total = 0;
f->tp = tp;
+ f->name = name;
f->do_crc = 0;
SHA1_Init(&f->ctx);
return f;
/* A SHA1-protected file */
struct sha1file {
- int fd, error;
- unsigned int offset, namelen;
+ int fd;
+ unsigned int offset;
SHA_CTX ctx;
+ off_t total;
struct progress *tp;
- char name[PATH_MAX];
+ const char *name;
int do_crc;
uint32_t crc32;
unsigned char buffer[8192];
{ "receive-pack", "receivepack", receive_pack, 0, 1 },
};
-static void enable_service(const char *name, int ena) {
+static void enable_service(const char *name, int ena)
+{
int i;
for (i = 0; i < ARRAY_SIZE(daemon_service); i++) {
if (!strcmp(daemon_service[i].name, name)) {
die("No such service %s", name);
}
-static void make_service_overridable(const char *name, int ena) {
+static void make_service_overridable(const char *name, int ena)
+{
int i;
for (i = 0; i < ARRAY_SIZE(daemon_service); i++) {
if (!strcmp(daemon_service[i].name, name)) {
return 0;
}
-static struct dir_entry *dir_entry_new(const char *pathname, int len) {
+static struct dir_entry *dir_entry_new(const char *pathname, int len)
+{
struct dir_entry *ent;
ent = xmalloc(sizeof(*ent) + len + 1);
#define PACK_ID_BITS 16
#define MAX_PACK_ID ((1<<PACK_ID_BITS)-1)
+#define DEPTH_BITS 13
+#define MAX_DEPTH ((1<<DEPTH_BITS)-1)
struct object_entry
{
struct object_entry *next;
uint32_t offset;
- unsigned type : TYPE_BITS;
- unsigned pack_id : PACK_ID_BITS;
+ uint32_t type : TYPE_BITS,
+ pack_id : PACK_ID_BITS,
+ depth : DEPTH_BITS;
unsigned char sha1[20];
};
unsigned pos = sizeof(hdr) - 1;
delta_count_by_type[type]++;
- last->depth++;
+ e->depth = last->depth + 1;
hdrlen = encode_header(OBJ_OFS_DELTA, deltalen, hdr);
write_or_die(pack_data->pack_fd, hdr, hdrlen);
write_or_die(pack_data->pack_fd, hdr + pos, sizeof(hdr) - pos);
pack_size += sizeof(hdr) - pos;
} else {
- if (last)
- last->depth = 0;
+ e->depth = 0;
hdrlen = encode_header(type, dat->len, hdr);
write_or_die(pack_data->pack_fd, hdr, hdrlen);
pack_size += hdrlen;
strbuf_swap(&last->data, dat);
}
last->offset = e->offset;
+ last->depth = e->depth;
}
return 0;
}
if (myoe && myoe->pack_id != MAX_PACK_ID) {
if (myoe->type != OBJ_TREE)
die("Not a tree: %s", sha1_to_hex(sha1));
- t->delta_depth = 0;
+ t->delta_depth = myoe->depth;
buf = gfi_unpack_entry(myoe, &size);
} else {
enum object_type type;
}
else if (!prefixcmp(a, "--max-pack-size="))
max_packsize = strtoumax(a + 16, NULL, 0) * 1024 * 1024;
- else if (!prefixcmp(a, "--depth="))
+ else if (!prefixcmp(a, "--depth=")) {
max_depth = strtoul(a + 8, NULL, 0);
+ if (max_depth > MAX_DEPTH)
+ die("--depth cannot exceed %u", MAX_DEPTH);
+ }
else if (!prefixcmp(a, "--active-branches="))
max_active_branches = strtoul(a + 18, NULL, 0);
else if (!prefixcmp(a, "--import-marks="))
if expr "$_tried" : ".*[|].*" > /dev/null ; then
echo "There are only 'skip'ped commit left to test."
echo "The first bad commit could be any of:"
- echo "$_tried" | sed -e 's/[|]/\n/g'
+ echo "$_tried" | sed -e 's/[|]/\
+/g'
echo "We cannot bisect more!"
exit 2
fi
ignored=
ignoredonly=
cleandir=
-disabled="`git config --bool clean.requireForce`"
rmf="rm -f --"
rmrf="rm -rf --"
rm_refuse="echo Not removing"
echo1="echo"
+disabled=$(git config --bool clean.requireForce)
+
while test $# != 0
do
case "$1" in
cleandir=1
;;
-f)
- disabled=
+ disabled=false
;;
-n)
- disabled=
+ disabled=false
rmf="echo Would remove"
rmrf="echo Would remove"
rm_refuse="echo Would not remove"
shift
done
-if [ "$disabled" = true ]; then
- echo "clean.requireForce set and -n or -f not given; refusing to clean"
- exit 1
-fi
+# requireForce used to default to false but now it defaults to true.
+# IOW, lack of explicit "clean.requireForce = false" is taken as
+# "clean.requireForce = true".
+case "$disabled" in
+"")
+ die "clean.requireForce not set and -n or -f not given; refusing to clean"
+ ;;
+"true")
+ die "clean.requireForce set and -n or -f not given; refusing to clean"
+ ;;
+esac
case "$ignored,$ignoredonly" in
1,1) usage;;
if [ -z "$ignored" ]; then
excl="--exclude-per-directory=.gitignore"
+ excl_info= excludes_file=
if [ -f "$GIT_DIR/info/exclude" ]; then
excl_info="--exclude-from=$GIT_DIR/info/exclude"
fi
+ if cfg_excl=$(git config core.excludesfile) && test -f "$cfg_excl"
+ then
+ excludes_file="--exclude-from=$cfg_excl"
+ fi
if [ "$ignoredonly" ]; then
excl="$excl --ignored"
fi
fi
-git ls-files --others --directory $excl ${excl_info:+"$excl_info"} -- "$@" |
+git ls-files --others --directory \
+ $excl ${excl_info:+"$excl_info"} ${excludes_file:+"$excludes_file"} \
+ -- "$@" |
while read -r file; do
if [ -d "$file" -a ! -L "$file" ]; then
if [ -z "$cleandir" ]; then
const void *needle, size_t needlelen);
#endif
+#ifdef __GLIBC_PREREQ
+#if __GLIBC_PREREQ(2, 1)
+#define HAVE_STRCHRNUL
+#endif
+#endif
+
+#ifndef HAVE_STRCHRNUL
+#define strchrnul gitstrchrnul
+static inline char *gitstrchrnul(const char *s, int c)
+{
+ while (*s && *s != c)
+ s++;
+ return (char *)s;
+}
+#endif
+
extern void release_pack_memory(size_t, int);
static inline char* xstrdup(const char *str)
#!/usr/bin/perl -w
-# Known limitations:
-# - does not propagate permissions
-# - error handling has not been extensively tested
-#
-
use strict;
use Getopt::Std;
use File::Temp qw(tempdir);
use Data::Dumper;
use File::Basename qw(basename dirname);
-unless ($ENV{GIT_DIR} && -r $ENV{GIT_DIR}){
- die "GIT_DIR is not defined or is unreadable";
-}
-
-our ($opt_h, $opt_P, $opt_p, $opt_v, $opt_c, $opt_f, $opt_a, $opt_m, $opt_d, $opt_u);
+our ($opt_h, $opt_P, $opt_p, $opt_v, $opt_c, $opt_f, $opt_a, $opt_m, $opt_d, $opt_u, $opt_w);
-getopts('uhPpvcfam:d:');
+getopts('uhPpvcfam:d:w:');
$opt_h && usage();
die "Need at least one commit identifier!" unless @ARGV;
+if ($opt_w) {
+ unless ($ENV{GIT_DIR}) {
+ # Remember where our GIT_DIR is before changing to CVS checkout
+ my $gd =`git-rev-parse --git-dir`;
+ chomp($gd);
+ if ($gd eq '.git') {
+ my $wd = `pwd`;
+ chomp($wd);
+ $gd = $wd."/.git" ;
+ }
+ $ENV{GIT_DIR} = $gd;
+ }
+
+ if (! -d $opt_w."/CVS" ) {
+ die "$opt_w is not a CVS checkout";
+ }
+ chdir $opt_w or die "Cannot change to CVS checkout at $opt_w";
+}
+unless ($ENV{GIT_DIR} && -r $ENV{GIT_DIR}){
+ die "GIT_DIR is not defined or is unreadable";
+}
+
+
my @cvs;
if ($opt_d) {
@cvs = ('cvs', '-d', $opt_d);
print "You'll need to apply the patch in .cvsexportcommit.diff manually\n";
print "using a patch program. After applying the patch and resolving the\n";
print "problems you may commit using:";
+ print "\n cd \"$opt_w\"" if $opt_w;
print "\n $cmd\n\n";
exit(1);
}
sub usage {
print STDERR <<END;
-Usage: GIT_DIR=/path/to/.git ${\basename $0} [-h] [-p] [-v] [-c] [-f] [-m msgprefix] [ parent ] commit
+Usage: GIT_DIR=/path/to/.git ${\basename $0} [-h] [-p] [-v] [-c] [-f] [-u] [-w cvsworkdir] [-m msgprefix] [ parent ] commit
END
exit(1);
}
}
}
- $user="anonymous" unless defined $user;
+ # if username is not explicit in CVSROOT, then use current user, as cvs would
+ $user=(getlogin() || $ENV{'LOGNAME'} || $ENV{'USER'} || "anonymous") unless $user;
my $rr2 = "-";
unless ($port) {
$rr2 = ":pserver:$user\@$serv:$repo";
port=`git config --get instaweb.port`
module_path="`git config --get instaweb.modulepath`"
-conf=$GIT_DIR/gitweb/httpd.conf
+conf="$GIT_DIR/gitweb/httpd.conf"
# Defaults:
httpd_only="`echo $httpd | cut -f1 -d' '`"
if case "$httpd_only" in /*) : ;; *) which $httpd_only >/dev/null;; esac
then
- $httpd $fqgitdir/gitweb/httpd.conf
+ $httpd "$fqgitdir/gitweb/httpd.conf"
else
# many httpds are installed in /usr/sbin or /usr/local/sbin
# these days and those are not in most users $PATHs
cgi.assign = ( ".cgi" => "" )
mimetype.assign = ( ".css" => "text/css" )
EOF
- test "$local" = true && echo 'server.bind = "127.0.0.1"' >> "$conf"
+ test x"$local" = xtrue && echo 'server.bind = "127.0.0.1"' >> "$conf"
}
apache2_conf () {
test -z "$module_path" && module_path=/usr/lib/apache2/modules
mkdir -p "$GIT_DIR/gitweb/logs"
bind=
- test "$local" = true && bind='127.0.0.1:'
+ test x"$local" = xtrue && bind='127.0.0.1:'
echo 'text/css css' > $fqgitdir/mime.types
cat > "$conf" <<EOF
ServerName "git-instaweb"
}
script='
-s#^\(my\|our\) $projectroot =.*#\1 $projectroot = "'`dirname $fqgitdir`'";#
+s#^\(my\|our\) $projectroot =.*#\1 $projectroot = "'$(dirname "$fqgitdir")'";#
s#\(my\|our\) $gitbin =.*#\1 $gitbin = "'$GIT_EXEC_PATH'";#
s#\(my\|our\) $projects_list =.*#\1 $projects_list = $projectroot;#
s#\(my\|our\) $git_temp =.*#\1 $git_temp = "'$fqgitdir/gitweb/tmp'";#'
EOFGITWEB
}
-gitweb_cgi $GIT_DIR/gitweb/gitweb.cgi
-gitweb_css $GIT_DIR/gitweb/gitweb.css
+gitweb_cgi "$GIT_DIR/gitweb/gitweb.cgi"
+gitweb_css "$GIT_DIR/gitweb/gitweb.css"
case "$httpd" in
*lighttpd*)
esac
start_httpd
-test -z "$browser" && browser=echo
url=http://127.0.0.1:$port
-$browser $url || echo $url
+"$browser" $url || echo $url
SUBDIRECTORY_OK='Yes'
. git-sh-setup
+echo "WARNING: '$0' is deprecated in favor of 'git fsck --lost-found'" >&2
+
if [ "$#" != "0" ]
then
usage
dropsave() {
rm -f -- "$GIT_DIR/MERGE_HEAD" "$GIT_DIR/MERGE_MSG" \
- "$GIT_DIR/MERGE_SAVE" || exit 1
+ "$GIT_DIR/MERGE_STASH" || exit 1
}
savestate() {
# Stash away any local modifications.
- git diff-index -z --name-only $head |
- cpio -0 -o >"$GIT_DIR/MERGE_SAVE"
+ git stash create >"$GIT_DIR/MERGE_STASH"
}
restorestate() {
- if test -f "$GIT_DIR/MERGE_SAVE"
+ if test -f "$GIT_DIR/MERGE_STASH"
then
git reset --hard $head >/dev/null
- cpio -iuv <"$GIT_DIR/MERGE_SAVE"
+ git stash apply $(cat "$GIT_DIR/MERGE_STASH")
git update-index --refresh >/dev/null
fi
}
single_strategy=no
;;
*)
- rm -f "$GIT_DIR/MERGE_SAVE"
+ rm -f "$GIT_DIR/MERGE_STASH"
single_strategy=yes
;;
esac
-s|--strategy)
case "$#,$1" in
*,*=*)
- STRATEGY="-s `expr "z$1" : 'z-[^=]*=\(.*\)'`" ;;
+ STRATEGY="-s "$(expr "z$1" : 'z-[^=]*=\(.*\)') ;;
1,*)
usage ;;
*)
cmt="$(cat "$dotest/cmt.$1")"
echo "$cmt" > "$dotest/current"
hd=$(git rev-parse --verify HEAD)
- cmt_name=$(git symbolic-ref HEAD)
+ cmt_name=$(git symbolic-ref HEAD 2> /dev/null || echo HEAD)
msgnum=$(cat "$dotest/msgnum")
end=$(cat "$dotest/end")
eval GITHEAD_$cmt='"${cmt_name##refs/heads/}~$(($end - $msgnum))"'
esac
}
+move_to_original_branch () {
+ test -z "$head_name" &&
+ head_name="$(cat "$dotest"/head-name)" &&
+ onto="$(cat "$dotest"/onto)" &&
+ orig_head="$(cat "$dotest"/orig-head)"
+ case "$head_name" in
+ refs/*)
+ message="rebase finished: $head_name onto $onto"
+ git update-ref -m "$message" \
+ $head_name $(git rev-parse HEAD) $orig_head &&
+ git symbolic-ref HEAD $head_name ||
+ die "Could not move back to $head_name"
+ ;;
+ esac
+}
+
finish_rb_merge () {
+ move_to_original_branch
rm -r "$dotest"
echo "All done."
}
finish_rb_merge
exit
fi
- git am --resolved --3way --resolvemsg="$RESOLVEMSG"
+ head_name=$(cat .dotest/head-name) &&
+ onto=$(cat .dotest/onto) &&
+ orig_head=$(cat .dotest/orig-head) &&
+ git am --resolved --3way --resolvemsg="$RESOLVEMSG" &&
+ move_to_original_branch
exit
;;
--skip)
finish_rb_merge
exit
fi
- git am -3 --skip --resolvemsg="$RESOLVEMSG"
+ head_name=$(cat .dotest/head-name) &&
+ onto=$(cat .dotest/onto) &&
+ orig_head=$(cat .dotest/orig-head) &&
+ git am -3 --skip --resolvemsg="$RESOLVEMSG" &&
+ move_to_original_branch
exit
;;
--abort)
git rerere clear
if test -d "$dotest"
then
+ move_to_original_branch
rm -r "$dotest"
elif test -d .dotest
then
+ dotest=.dotest
+ move_to_original_branch
rm -r .dotest
else
die "No rebase in progress?"
GIT_PAGER='' git diff --stat --summary "$mb" "$onto"
fi
+# move to a detached HEAD
+orig_head=$(git rev-parse HEAD^0)
+head_name=$(git symbolic-ref HEAD 2> /dev/null)
+case "$head_name" in
+'')
+ head_name="detached HEAD"
+ ;;
+*)
+ git checkout "$orig_head" > /dev/null 2>&1 ||
+ die "could not detach HEAD"
+ ;;
+esac
+
# Rewind the head to "$onto"; this saves our current head in ORIG_HEAD.
echo "First, rewinding head to replay your work on top of it..."
git-reset --hard "$onto"
if test "$mb" = "$branch"
then
echo >&2 "Fast-forwarded $branch_name to $onto_name."
+ move_to_original_branch
exit 0
fi
if test -z "$do_merge"
then
git format-patch -k --stdout --full-index --ignore-if-in-upstream "$upstream"..ORIG_HEAD |
- git am $git_am_opt --binary -3 -k --resolvemsg="$RESOLVEMSG"
- exit $?
+ git am $git_am_opt --binary -3 -k --resolvemsg="$RESOLVEMSG" &&
+ move_to_original_branch
+ ret=$?
+ test 0 != $ret -a -d .dotest &&
+ echo $head_name > .dotest/head-name &&
+ echo $onto > .dotest/onto &&
+ echo $orig_head > .dotest/orig-head
+ exit $ret
fi
# start doing a rebase with git-merge
mkdir -p "$dotest"
echo "$onto" > "$dotest/onto"
echo "$onto_name" > "$dotest/onto_name"
-prev_head=`git rev-parse HEAD^0`
+prev_head=$orig_head
echo "$prev_head" > "$dotest/prev_head"
+echo "$orig_head" > "$dotest/orig-head"
+echo "$head_name" > "$dotest/head-name"
msgnum=0
for cmt in `git rev-list --reverse --no-merges "$upstream"..ORIG_HEAD`
merge_base=`git merge-base $baserev $headrev` ||
die "fatal: No commits in common between $base and $head"
-url="`get_remote_url "$url"`"
-branch=`git peek-remote "$url" \
+url=$(get_remote_url "$url")
+branch=$(git peek-remote "$url" \
| sed -n -e "/^$headrev refs.heads./{
s/^.* refs.heads.//
p
q
- }"`
+ }")
if [ -z "$branch" ]; then
echo "warn: No branch of $url is at:" >&2
git log --max-count=1 --pretty='format:warn: %h: %s' $headrev >&2
--smtp-ssl If set, connects to the SMTP server using SSL.
- --suppress-from Suppress sending emails to yourself if your address
- appears in a From: line. Defaults to off.
+ --suppress-from Suppress sending emails to yourself. Defaults to off.
--thread Specify that the "In-Reply-To:" header should be set on all
emails. Defaults to on.
if (!defined $initial_subject && $compose) {
do {
- $_ = $term->readline("What subject should the emails start with? ",
+ $_ = $term->readline("What subject should the initial email start with? ",
$initial_subject);
} while (!defined $_);
$initial_subject = $_;
if (/^(Signed-off-by|Cc): (.*)$/i && $signed_off_cc) {
my $c = $2;
chomp $c;
+ next if ($c eq $sender and $suppress_from);
push @cc, $c;
printf("(sob) Adding cc: %s from line '%s'\n",
$c, $_) unless $quiet;
my $c = $_;
$c =~ s/^\s*//g;
$c =~ s/\n$//g;
+ next if ($c eq $sender and $suppress_from);
push @cc, $c;
printf("(cc-cmd) Adding cc: %s from: '%s'\n",
$c, $cc_cmd) unless $quiet;
clear_stash () {
if current=$(git rev-parse --verify $ref_stash 2>/dev/null)
then
- git update-ref -d refs/stash $current
+ git update-ref -d $ref_stash $current
fi
}
-save_stash () {
+create_stash () {
stash_msg="$1"
if no_changes
then
- echo >&2 'No local changes to save'
exit 0
fi
- test -f "$GIT_DIR/logs/$ref_stash" ||
- clear_stash || die "Cannot initialize stash"
-
- # Make sure the reflog for stash is kept.
- : >>"$GIT_DIR/logs/$ref_stash"
# state of the base commit
if b_commit=$(git rev-parse --verify HEAD)
w_commit=$(printf '%s\n' "$stash_msg" |
git commit-tree $w_tree -p $b_commit -p $i_commit) ||
die "Cannot record working tree state"
+}
+
+save_stash () {
+ stash_msg="$1"
+
+ if no_changes
+ then
+ echo >&2 'No local changes to save'
+ exit 0
+ fi
+ test -f "$GIT_DIR/logs/$ref_stash" ||
+ clear_stash || die "Cannot initialize stash"
+
+ create_stash "$stash_msg"
+
+ # Make sure the reflog for stash is kept.
+ : >>"$GIT_DIR/logs/$ref_stash"
git update-ref -m "$stash_msg" $ref_stash $w_commit ||
die "Cannot save the current status"
clear)
clear_stash
;;
+create)
+ if test $# -gt 0 && test "$1" = create
+ then
+ shift
+ fi
+ create_stash "$*" && echo "$w_commit"
+ ;;
help | usage)
usage
;;
module_name()
{
# Do we have "submodule.<something>.path = $1" defined in .gitmodules file?
- re=$(printf '%s' "$1" | sed -e 's/\([^a-zA-Z0-9_]\)/\\\1/g')
+ re=$(printf '%s' "$1" | sed -e 's/[].[^$\\*]/\\&/g')
name=$( GIT_CONFIG=.gitmodules \
git config --get-regexp '^submodule\..*\.path$' |
sed -n -e 's|^submodule\.\(.*\)\.path '"$re"'$|\1|p' )
sub cmd_dcommit {
my $head = shift;
+ git_cmd_try { command_oneline(qw/diff-index --quiet HEAD/) }
+ 'Cannot dcommit with a dirty index. Commit your changes first'
+ . "or stash them with `git stash'.\n";
$head ||= 'HEAD';
my @refs;
my ($url, $rev, $uuid, $gs) = working_head_info($head, \@refs);
]
}
+sub escape_uri_only {
+ my ($uri) = @_;
+ my @tmp;
+ foreach (split m{/}, $uri) {
+ s/([^\w.-])/sprintf("%%%02X",ord($1))/eg;
+ push @tmp, $_;
+ }
+ join('/', @tmp);
+}
+
+sub escape_url {
+ my ($url) = @_;
+ if ($url =~ m#^(https?)://([^/]+)(.*)$#) {
+ my ($scheme, $domain, $uri) = ($1, $2, escape_uri_only($3));
+ $url = "$scheme://$domain$uri";
+ }
+ $url;
+}
+
sub new {
my ($class, $url) = @_;
$url =~ s!/+$!!;
$Git::SVN::Prompt::_no_auth_cache = 1;
}
} # no warnings 'once'
- my $self = SVN::Ra->new(url => $url, auth => $baton,
+ my $self = SVN::Ra->new(url => escape_url($url), auth => $baton,
config => $config,
pool => SVN::Pool->new,
auth_provider_callbacks => $callbacks);
+ $self->{url} = $url;
$self->{svn_path} = $url;
$self->{repos_root} = $self->get_repos_root;
$self->{svn_path} =~ s#^\Q$self->{repos_root}\E(/|$)##;
my $full_url = $self->{url};
my $old_url = $full_url;
- $full_url .= "/$path" if length $path;
+ $full_url .= '/' . escape_uri_only($path) if length $path;
my ($ra, $reparented);
if ($old_url ne $full_url) {
if ($old_url !~ m#^svn(\+ssh)?://#) {
prefix = setup_git_directory();
if (p->option & USE_PAGER)
setup_pager();
- if (p->option & NEED_WORK_TREE) {
- const char *work_tree = get_git_work_tree();
- const char *git_dir = get_git_dir();
- if (!is_absolute_path(git_dir))
- set_git_dir(make_absolute_path(git_dir));
- if (!work_tree || chdir(work_tree))
- die("%s must be run in a work tree", p->cmd);
- }
+ if (p->option & NEED_WORK_TREE)
+ setup_work_tree();
+
trace_argv_printf(argv, argc, "trace: built-in: git");
status = p->fn(argc, argv, prefix);
{ "rev-list", cmd_rev_list, RUN_SETUP },
{ "rev-parse", cmd_rev_parse, RUN_SETUP },
{ "revert", cmd_revert, RUN_SETUP | NEED_WORK_TREE },
- { "rm", cmd_rm, RUN_SETUP | NEED_WORK_TREE },
+ { "rm", cmd_rm, RUN_SETUP },
{ "runstatus", cmd_runstatus, RUN_SETUP | NEED_WORK_TREE },
{ "shortlog", cmd_shortlog, RUN_SETUP | USE_PAGER },
{ "show-branch", cmd_show_branch, RUN_SETUP },
$date{'mday-time'} = sprintf "%d %s %02d:%02d",
$mday, $months[$mon], $hour ,$min;
$date{'iso-8601'} = sprintf "%04d-%02d-%02dT%02d:%02d:%02dZ",
- 1900+$year, $mon, $mday, $hour ,$min, $sec;
+ 1900+$year, 1+$mon, $mday, $hour ,$min, $sec;
$tz =~ m/^([+\-][0-9][0-9])([0-9][0-9])$/;
my $local = $epoch + ((int $1 + ($2/60)) * 3600);
int prefix_length = -1;
int no_more_flags = 0;
+ git_config(git_default_config);
+
for (i = 1 ; i < argc; i++) {
if (!no_more_flags && argv[i][0] == '-') {
if (!strcmp(argv[i], "-t")) {
cmds->cnt = j;
}
-static void exclude_cmds(struct cmdnames *cmds, struct cmdnames *excludes) {
+static void exclude_cmds(struct cmdnames *cmds, struct cmdnames *excludes)
+{
int ci, cj, ei;
int cmp;
die("early EOF");
die("read error on input: %s", strerror(errno));
}
- if (from_stdin)
- display_throughput(progress, ret);
input_len += ret;
+ if (from_stdin)
+ display_throughput(progress, consumed_bytes + input_len);
} while (input_len < min);
return input_buffer;
}
static void *get_data_from_pack(struct object_entry *obj)
{
- unsigned long from = obj[0].idx.offset + obj[0].hdr_size;
+ off_t from = obj[0].idx.offset + obj[0].hdr_size;
unsigned long len = obj[1].idx.offset - from;
unsigned long rdy = 0;
unsigned char *src, *data;
flush();
} else {
if (fix_thin_pack) {
+ char msg[48];
int nr_unresolved = nr_deltas - nr_resolved_deltas;
int nr_objects_initial = nr_objects;
if (nr_unresolved <= 0)
(nr_objects + nr_unresolved + 1)
* sizeof(*objects));
fix_unresolved_deltas(nr_unresolved);
- stop_progress(&progress);
- if (verbose)
- fprintf(stderr, "%d objects were added to complete this thin pack.\n",
- nr_objects - nr_objects_initial);
+ sprintf(msg, "completed with %d local objects",
+ nr_objects - nr_objects_initial);
+ stop_progress_msg(&progress, msg);
fixup_pack_header_footer(output_fd, sha1,
- curr_pack, nr_objects);
+ curr_pack, nr_objects);
}
if (nr_deltas != nr_resolved_deltas)
die("pack has %d unresolved deltas",
}
for (i = 0; i < objects.nr; i++)
show_object(&objects.objects[i]);
+ free(objects.objects);
+ if (revs->pending.nr) {
+ free(revs->pending.objects);
+ revs->pending.nr = 0;
+ revs->pending.alloc = 0;
+ revs->pending.objects = NULL;
+ }
}
const struct option *options)
{
const char *arg_end = strchr(arg, '=');
- const struct option *abbrev_option = NULL;
- int abbrev_flags = 0;
+ const struct option *abbrev_option = NULL, *ambiguous_option = NULL;
+ int abbrev_flags = 0, ambiguous_flags = 0;
if (!arg_end)
arg_end = arg + strlen(arg);
/* abbreviated? */
if (!strncmp(options->long_name, arg, arg_end - arg)) {
is_abbreviated:
- if (abbrev_option)
- return error("Ambiguous option: %s "
- "(could be --%s%s or --%s%s)",
- arg,
- (flags & OPT_UNSET) ?
- "no-" : "",
- options->long_name,
- (abbrev_flags & OPT_UNSET) ?
- "no-" : "",
- abbrev_option->long_name);
+ if (abbrev_option) {
+ /*
+ * If this is abbreviated, it is
+ * ambiguous. So when there is no
+ * exact match later, we need to
+ * error out.
+ */
+ ambiguous_option = abbrev_option;
+ ambiguous_flags = abbrev_flags;
+ }
if (!(flags & OPT_UNSET) && *arg_end)
p->opt = arg_end + 1;
abbrev_option = options;
}
return get_value(p, options, flags);
}
+
+ if (ambiguous_option)
+ return error("Ambiguous option: %s "
+ "(could be --%s%s or --%s%s)",
+ arg,
+ (ambiguous_flags & OPT_UNSET) ? "no-" : "",
+ ambiguous_option->long_name,
+ (abbrev_flags & OPT_UNSET) ? "no-" : "",
+ abbrev_option->long_name);
if (abbrev_option)
return get_value(p, abbrev_option, abbrev_flags);
return error("unknown option `%s'", arg);
$self->wc_subdir() and chdir($self->wc_subdir());
}
_execv_git_cmd(@args);
- die "exec failed: $!";
+ die qq[exec "@args" failed: $!];
}
# Execute the given Git command ($_[0]) with arguments ($_[1..])
#include "cache.h"
#include "commit.h"
-#include "interpolate.h"
#include "utf8.h"
#include "diff.h"
#include "revision.h"
return out;
}
-static void fill_person(struct interp *table, const char *msg, int len)
+static void format_person_part(struct strbuf *sb, char part,
+ const char *msg, int len)
{
int start, end, tz = 0;
unsigned long date;
start = end + 1;
while (end > 0 && isspace(msg[end - 1]))
end--;
- table[0].value = xmemdupz(msg, end);
+ if (part == 'n') { /* name */
+ strbuf_add(sb, msg, end);
+ return;
+ }
if (start >= len)
return;
if (end >= len)
return;
- table[1].value = xmemdupz(msg + start, end - start);
+ if (part == 'e') { /* email */
+ strbuf_add(sb, msg + start, end - start);
+ return;
+ }
/* parse date */
for (start = end + 1; start < len && isspace(msg[start]); start++)
if (msg + start == ep)
return;
- table[5].value = xmemdupz(msg + start, ep - (msg + start));
+ if (part == 't') { /* date, UNIX timestamp */
+ strbuf_add(sb, msg + start, ep - (msg + start));
+ return;
+ }
/* parse tz */
for (start = ep - msg + 1; start < len && isspace(msg[start]); start++)
tz = -tz;
}
- interp_set_entry(table, 2, show_date(date, tz, DATE_NORMAL));
- interp_set_entry(table, 3, show_date(date, tz, DATE_RFC2822));
- interp_set_entry(table, 4, show_date(date, tz, DATE_RELATIVE));
- interp_set_entry(table, 6, show_date(date, tz, DATE_ISO8601));
+ switch (part) {
+ case 'd': /* date */
+ strbuf_addstr(sb, show_date(date, tz, DATE_NORMAL));
+ return;
+ case 'D': /* date, RFC2822 style */
+ strbuf_addstr(sb, show_date(date, tz, DATE_RFC2822));
+ return;
+ case 'r': /* date, relative */
+ strbuf_addstr(sb, show_date(date, tz, DATE_RELATIVE));
+ return;
+ case 'i': /* date, ISO 8601 */
+ strbuf_addstr(sb, show_date(date, tz, DATE_ISO8601));
+ return;
+ }
}
-void format_commit_message(const struct commit *commit,
- const void *format, struct strbuf *sb)
-{
- struct interp table[] = {
- { "%H" }, /* commit hash */
- { "%h" }, /* abbreviated commit hash */
- { "%T" }, /* tree hash */
- { "%t" }, /* abbreviated tree hash */
- { "%P" }, /* parent hashes */
- { "%p" }, /* abbreviated parent hashes */
- { "%an" }, /* author name */
- { "%ae" }, /* author email */
- { "%ad" }, /* author date */
- { "%aD" }, /* author date, RFC2822 style */
- { "%ar" }, /* author date, relative */
- { "%at" }, /* author date, UNIX timestamp */
- { "%ai" }, /* author date, ISO 8601 */
- { "%cn" }, /* committer name */
- { "%ce" }, /* committer email */
- { "%cd" }, /* committer date */
- { "%cD" }, /* committer date, RFC2822 style */
- { "%cr" }, /* committer date, relative */
- { "%ct" }, /* committer date, UNIX timestamp */
- { "%ci" }, /* committer date, ISO 8601 */
- { "%e" }, /* encoding */
- { "%s" }, /* subject */
- { "%b" }, /* body */
- { "%Cred" }, /* red */
- { "%Cgreen" }, /* green */
- { "%Cblue" }, /* blue */
- { "%Creset" }, /* reset color */
- { "%n" }, /* newline */
- { "%m" }, /* left/right/bottom */
- };
- enum interp_index {
- IHASH = 0, IHASH_ABBREV,
- ITREE, ITREE_ABBREV,
- IPARENTS, IPARENTS_ABBREV,
- IAUTHOR_NAME, IAUTHOR_EMAIL,
- IAUTHOR_DATE, IAUTHOR_DATE_RFC2822, IAUTHOR_DATE_RELATIVE,
- IAUTHOR_TIMESTAMP, IAUTHOR_ISO8601,
- ICOMMITTER_NAME, ICOMMITTER_EMAIL,
- ICOMMITTER_DATE, ICOMMITTER_DATE_RFC2822,
- ICOMMITTER_DATE_RELATIVE, ICOMMITTER_TIMESTAMP,
- ICOMMITTER_ISO8601,
- IENCODING,
- ISUBJECT,
- IBODY,
- IRED, IGREEN, IBLUE, IRESET_COLOR,
- INEWLINE,
- ILEFT_RIGHT,
- };
- struct commit_list *p;
- char parents[1024];
- unsigned long len;
- int i;
- enum { HEADER, SUBJECT, BODY } state;
- const char *msg = commit->buffer;
+struct chunk {
+ size_t off;
+ size_t len;
+};
- if (ILEFT_RIGHT + 1 != ARRAY_SIZE(table))
- die("invalid interp table!");
+struct format_commit_context {
+ const struct commit *commit;
+
+ /* These offsets are relative to the start of the commit message. */
+ int commit_header_parsed;
+ struct chunk subject;
+ struct chunk author;
+ struct chunk committer;
+ struct chunk encoding;
+ size_t body_off;
+
+ /* The following ones are relative to the result struct strbuf. */
+ struct chunk abbrev_commit_hash;
+ struct chunk abbrev_tree_hash;
+ struct chunk abbrev_parent_hashes;
+};
- /* these are independent of the commit */
- interp_set_entry(table, IRED, "\033[31m");
- interp_set_entry(table, IGREEN, "\033[32m");
- interp_set_entry(table, IBLUE, "\033[34m");
- interp_set_entry(table, IRESET_COLOR, "\033[m");
- interp_set_entry(table, INEWLINE, "\n");
+static int add_again(struct strbuf *sb, struct chunk *chunk)
+{
+ if (chunk->len) {
+ strbuf_adddup(sb, chunk->off, chunk->len);
+ return 1;
+ }
- /* these depend on the commit */
- if (!commit->object.parsed)
- parse_object(commit->object.sha1);
- interp_set_entry(table, IHASH, sha1_to_hex(commit->object.sha1));
- interp_set_entry(table, IHASH_ABBREV,
- find_unique_abbrev(commit->object.sha1,
- DEFAULT_ABBREV));
- interp_set_entry(table, ITREE, sha1_to_hex(commit->tree->object.sha1));
- interp_set_entry(table, ITREE_ABBREV,
- find_unique_abbrev(commit->tree->object.sha1,
- DEFAULT_ABBREV));
- interp_set_entry(table, ILEFT_RIGHT,
- (commit->object.flags & BOUNDARY)
- ? "-"
- : (commit->object.flags & SYMMETRIC_LEFT)
- ? "<"
- : ">");
-
- parents[1] = 0;
- for (i = 0, p = commit->parents;
- p && i < sizeof(parents) - 1;
- p = p->next)
- i += snprintf(parents + i, sizeof(parents) - i - 1, " %s",
- sha1_to_hex(p->item->object.sha1));
- interp_set_entry(table, IPARENTS, parents + 1);
-
- parents[1] = 0;
- for (i = 0, p = commit->parents;
- p && i < sizeof(parents) - 1;
- p = p->next)
- i += snprintf(parents + i, sizeof(parents) - i - 1, " %s",
- find_unique_abbrev(p->item->object.sha1,
- DEFAULT_ABBREV));
- interp_set_entry(table, IPARENTS_ABBREV, parents + 1);
+ /*
+ * We haven't seen this chunk before. Our caller is surely
+ * going to add it the hard way now. Remember the most likely
+ * start of the to-be-added chunk: the current end of the
+ * struct strbuf.
+ */
+ chunk->off = sb->len;
+ return 0;
+}
+
+static void parse_commit_header(struct format_commit_context *context)
+{
+ const char *msg = context->commit->buffer;
+ int i;
+ enum { HEADER, SUBJECT, BODY } state;
for (i = 0, state = HEADER; msg[i] && state < BODY; i++) {
int eol;
; /* do nothing */
if (state == SUBJECT) {
- table[ISUBJECT].value = xmemdupz(msg + i, eol - i);
+ context->subject.off = i;
+ context->subject.len = eol - i;
i = eol;
}
if (i == eol) {
/* strip empty lines */
while (msg[eol + 1] == '\n')
eol++;
- } else if (!prefixcmp(msg + i, "author "))
- fill_person(table + IAUTHOR_NAME,
- msg + i + 7, eol - i - 7);
- else if (!prefixcmp(msg + i, "committer "))
- fill_person(table + ICOMMITTER_NAME,
- msg + i + 10, eol - i - 10);
- else if (!prefixcmp(msg + i, "encoding "))
- table[IENCODING].value =
- xmemdupz(msg + i + 9, eol - i - 9);
+ } else if (!prefixcmp(msg + i, "author ")) {
+ context->author.off = i + 7;
+ context->author.len = eol - i - 7;
+ } else if (!prefixcmp(msg + i, "committer ")) {
+ context->committer.off = i + 10;
+ context->committer.len = eol - i - 10;
+ } else if (!prefixcmp(msg + i, "encoding ")) {
+ context->encoding.off = i + 9;
+ context->encoding.len = eol - i - 9;
+ }
i = eol;
}
- if (msg[i])
- table[IBODY].value = xstrdup(msg + i);
+ context->body_off = i;
+ context->commit_header_parsed = 1;
+}
+
+static void format_commit_item(struct strbuf *sb, const char *placeholder,
+ void *context)
+{
+ struct format_commit_context *c = context;
+ const struct commit *commit = c->commit;
+ const char *msg = commit->buffer;
+ struct commit_list *p;
+
+ /* these are independent of the commit */
+ switch (placeholder[0]) {
+ case 'C':
+ switch (placeholder[3]) {
+ case 'd': /* red */
+ strbuf_addstr(sb, "\033[31m");
+ return;
+ case 'e': /* green */
+ strbuf_addstr(sb, "\033[32m");
+ return;
+ case 'u': /* blue */
+ strbuf_addstr(sb, "\033[34m");
+ return;
+ case 's': /* reset color */
+ strbuf_addstr(sb, "\033[m");
+ return;
+ }
+ case 'n': /* newline */
+ strbuf_addch(sb, '\n');
+ return;
+ }
- len = interpolate(sb->buf + sb->len, strbuf_avail(sb),
- format, table, ARRAY_SIZE(table));
- if (len > strbuf_avail(sb)) {
- strbuf_grow(sb, len);
- interpolate(sb->buf + sb->len, strbuf_avail(sb) + 1,
- format, table, ARRAY_SIZE(table));
+ /* these depend on the commit */
+ if (!commit->object.parsed)
+ parse_object(commit->object.sha1);
+
+ switch (placeholder[0]) {
+ case 'H': /* commit hash */
+ strbuf_addstr(sb, sha1_to_hex(commit->object.sha1));
+ return;
+ case 'h': /* abbreviated commit hash */
+ if (add_again(sb, &c->abbrev_commit_hash))
+ return;
+ strbuf_addstr(sb, find_unique_abbrev(commit->object.sha1,
+ DEFAULT_ABBREV));
+ c->abbrev_commit_hash.len = sb->len - c->abbrev_commit_hash.off;
+ return;
+ case 'T': /* tree hash */
+ strbuf_addstr(sb, sha1_to_hex(commit->tree->object.sha1));
+ return;
+ case 't': /* abbreviated tree hash */
+ if (add_again(sb, &c->abbrev_tree_hash))
+ return;
+ strbuf_addstr(sb, find_unique_abbrev(commit->tree->object.sha1,
+ DEFAULT_ABBREV));
+ c->abbrev_tree_hash.len = sb->len - c->abbrev_tree_hash.off;
+ return;
+ case 'P': /* parent hashes */
+ for (p = commit->parents; p; p = p->next) {
+ if (p != commit->parents)
+ strbuf_addch(sb, ' ');
+ strbuf_addstr(sb, sha1_to_hex(p->item->object.sha1));
+ }
+ return;
+ case 'p': /* abbreviated parent hashes */
+ if (add_again(sb, &c->abbrev_parent_hashes))
+ return;
+ for (p = commit->parents; p; p = p->next) {
+ if (p != commit->parents)
+ strbuf_addch(sb, ' ');
+ strbuf_addstr(sb, find_unique_abbrev(
+ p->item->object.sha1, DEFAULT_ABBREV));
+ }
+ c->abbrev_parent_hashes.len = sb->len -
+ c->abbrev_parent_hashes.off;
+ return;
+ case 'm': /* left/right/bottom */
+ strbuf_addch(sb, (commit->object.flags & BOUNDARY)
+ ? '-'
+ : (commit->object.flags & SYMMETRIC_LEFT)
+ ? '<'
+ : '>');
+ return;
+ }
+
+ /* For the rest we have to parse the commit header. */
+ if (!c->commit_header_parsed)
+ parse_commit_header(c);
+
+ switch (placeholder[0]) {
+ case 's':
+ strbuf_add(sb, msg + c->subject.off, c->subject.len);
+ return;
+ case 'a':
+ format_person_part(sb, placeholder[1],
+ msg + c->author.off, c->author.len);
+ return;
+ case 'c':
+ format_person_part(sb, placeholder[1],
+ msg + c->committer.off, c->committer.len);
+ return;
+ case 'e':
+ strbuf_add(sb, msg + c->encoding.off, c->encoding.len);
+ return;
+ case 'b':
+ strbuf_addstr(sb, msg + c->body_off);
+ return;
}
- strbuf_setlen(sb, sb->len + len);
- interp_clear_table(table, ARRAY_SIZE(table));
+}
+
+void format_commit_message(const struct commit *commit,
+ const void *format, struct strbuf *sb)
+{
+ const char *placeholders[] = {
+ "H", /* commit hash */
+ "h", /* abbreviated commit hash */
+ "T", /* tree hash */
+ "t", /* abbreviated tree hash */
+ "P", /* parent hashes */
+ "p", /* abbreviated parent hashes */
+ "an", /* author name */
+ "ae", /* author email */
+ "ad", /* author date */
+ "aD", /* author date, RFC2822 style */
+ "ar", /* author date, relative */
+ "at", /* author date, UNIX timestamp */
+ "ai", /* author date, ISO 8601 */
+ "cn", /* committer name */
+ "ce", /* committer email */
+ "cd", /* committer date */
+ "cD", /* committer date, RFC2822 style */
+ "cr", /* committer date, relative */
+ "ct", /* committer date, UNIX timestamp */
+ "ci", /* committer date, ISO 8601 */
+ "e", /* encoding */
+ "s", /* subject */
+ "b", /* body */
+ "Cred", /* red */
+ "Cgreen", /* green */
+ "Cblue", /* blue */
+ "Creset", /* reset color */
+ "n", /* newline */
+ "m", /* left/right/bottom */
+ NULL
+ };
+ struct format_commit_context context;
+
+ memset(&context, 0, sizeof(context));
+ context.commit = commit;
+ strbuf_expand(sb, format, placeholders, format_commit_item, &context);
}
static void pp_header(enum cmit_fmt fmt,
#define TP_IDX_MAX 8
struct throughput {
+ off_t curr_total;
+ off_t prev_total;
struct timeval prev_tv;
- off_t total;
- unsigned long count;
- unsigned long avg_bytes;
- unsigned long last_bytes[TP_IDX_MAX];
+ unsigned int avg_bytes;
unsigned int avg_misecs;
+ unsigned int last_bytes[TP_IDX_MAX];
unsigned int last_misecs[TP_IDX_MAX];
unsigned int idx;
char display[32];
progress_update = 0;
}
-static int display(struct progress *progress, unsigned n, int done)
+static int display(struct progress *progress, unsigned n, const char *done)
{
- char *eol, *tp;
+ const char *eol, *tp;
if (progress->delay) {
if (!progress_update || --progress->delay)
progress->last_value = n;
tp = (progress->throughput) ? progress->throughput->display : "";
- eol = done ? ", done. \n" : " \r";
+ eol = done ? done : " \r";
if (progress->total) {
unsigned percent = n * 100 / progress->total;
if (percent != progress->last_percent || progress_update) {
return 0;
}
-void display_throughput(struct progress *progress, unsigned long n)
+static void throughput_string(struct throughput *tp, off_t total,
+ unsigned int rate)
+{
+ int l = sizeof(tp->display);
+ if (total > 1 << 30) {
+ l -= snprintf(tp->display, l, ", %u.%2.2u GiB",
+ (int)(total >> 30),
+ (int)(total & ((1 << 30) - 1)) / 10737419);
+ } else if (total > 1 << 20) {
+ l -= snprintf(tp->display, l, ", %u.%2.2u MiB",
+ (int)(total >> 20),
+ ((int)(total & ((1 << 20) - 1)) * 100) >> 20);
+ } else if (total > 1 << 10) {
+ l -= snprintf(tp->display, l, ", %u.%2.2u KiB",
+ (int)(total >> 10),
+ ((int)(total & ((1 << 10) - 1)) * 100) >> 10);
+ } else {
+ l -= snprintf(tp->display, l, ", %u bytes", (int)total);
+ }
+ if (rate)
+ snprintf(tp->display + sizeof(tp->display) - l, l,
+ " | %u KiB/s", rate);
+}
+
+void display_throughput(struct progress *progress, off_t total)
{
struct throughput *tp;
struct timeval tv;
if (!tp) {
progress->throughput = tp = calloc(1, sizeof(*tp));
- if (tp)
+ if (tp) {
+ tp->prev_total = tp->curr_total = total;
tp->prev_tv = tv;
+ }
return;
}
-
- tp->total += n;
- tp->count += n;
+ tp->curr_total = total;
/*
* We have x = bytes and y = microsecs. We want z = KiB/s:
misecs += (int)(tv.tv_usec - tp->prev_tv.tv_usec) / 977;
if (misecs > 512) {
- int l = sizeof(tp->display);
+ unsigned int count, rate;
+
+ count = total - tp->prev_total;
+ tp->prev_total = total;
tp->prev_tv = tv;
- tp->avg_bytes += tp->count;
+ tp->avg_bytes += count;
tp->avg_misecs += misecs;
-
- if (tp->total > 1 << 30) {
- l -= snprintf(tp->display, l, ", %u.%2.2u GiB",
- (int)(tp->total >> 30),
- (int)(tp->total & ((1 << 30) - 1)) / 10737419);
- } else if (tp->total > 1 << 20) {
- l -= snprintf(tp->display, l, ", %u.%2.2u MiB",
- (int)(tp->total >> 20),
- ((int)(tp->total & ((1 << 20) - 1))
- * 100) >> 20);
- } else if (tp->total > 1 << 10) {
- l -= snprintf(tp->display, l, ", %u.%2.2u KiB",
- (int)(tp->total >> 10),
- ((int)(tp->total & ((1 << 10) - 1))
- * 100) >> 10);
- } else {
- l -= snprintf(tp->display, l, ", %u bytes",
- (int)tp->total);
- }
- snprintf(tp->display + sizeof(tp->display) - l, l,
- " | %lu KiB/s", tp->avg_bytes / tp->avg_misecs);
-
+ rate = tp->avg_bytes / tp->avg_misecs;
tp->avg_bytes -= tp->last_bytes[tp->idx];
tp->avg_misecs -= tp->last_misecs[tp->idx];
- tp->last_bytes[tp->idx] = tp->count;
+ tp->last_bytes[tp->idx] = count;
tp->last_misecs[tp->idx] = misecs;
tp->idx = (tp->idx + 1) % TP_IDX_MAX;
- tp->count = 0;
+ throughput_string(tp, total, rate);
if (progress->last_value != -1 && progress_update)
- display(progress, progress->last_value, 0);
+ display(progress, progress->last_value, NULL);
}
}
int display_progress(struct progress *progress, unsigned n)
{
- return progress ? display(progress, n, 0) : 0;
+ return progress ? display(progress, n, NULL) : 0;
}
struct progress *start_progress_delay(const char *title, unsigned total,
}
void stop_progress(struct progress **p_progress)
+{
+ stop_progress_msg(p_progress, "done");
+}
+
+void stop_progress_msg(struct progress **p_progress, const char *msg)
{
struct progress *progress = *p_progress;
if (!progress)
*p_progress = NULL;
if (progress->last_value != -1) {
/* Force the last update */
+ char buf[strlen(msg) + 5];
+ struct throughput *tp = progress->throughput;
+ if (tp) {
+ unsigned int rate = !tp->avg_misecs ? 0 :
+ tp->avg_bytes / tp->avg_misecs;
+ throughput_string(tp, tp->curr_total, rate);
+ }
progress_update = 1;
- display(progress, progress->last_value, 1);
+ sprintf(buf, ", %s.\n", msg);
+ display(progress, progress->last_value, buf);
}
clear_progress_signal();
free(progress->throughput);
struct progress;
-void display_throughput(struct progress *progress, unsigned long n);
+void display_throughput(struct progress *progress, off_t total);
int display_progress(struct progress *progress, unsigned n);
struct progress *start_progress(const char *title, unsigned total);
struct progress *start_progress_delay(const char *title, unsigned total,
unsigned percent_treshold, unsigned delay);
void stop_progress(struct progress **progress);
+void stop_progress_msg(struct progress **progress, const char *msg);
#endif
/* 0x80 */ /* set to 0 */
};
-static inline int sq_must_quote(char c) {
+static inline int sq_must_quote(char c)
+{
return sq_lookup[(unsigned char)c] + quote_path_fully > 0;
}
return NULL;
}
+void setup_work_tree(void)
+{
+ const char *work_tree, *git_dir;
+ static int initialized = 0;
+
+ if (initialized)
+ return;
+ work_tree = get_git_work_tree();
+ git_dir = get_git_dir();
+ if (!is_absolute_path(git_dir))
+ set_git_dir(make_absolute_path(git_dir));
+ if (!work_tree || chdir(work_tree))
+ die("This operation must be run in a work tree");
+ initialized = 1;
+}
+
/*
* We cannot decide in this function whether we are in the work tree or
* not, since the config can only be read _after_ this function was called.
* stream, aka "verbose"). A message over band #3 is a signal that
* the remote died unexpectedly. A flush() concludes the stream.
*/
+
+#define PREFIX "remote:"
+#define SUFFIX "\033[K" /* change to " " if ANSI sequences don't work */
+
int recv_sideband(const char *me, int in_stream, int out, int err)
{
- char buf[7 + LARGE_PACKET_MAX + 1];
- strcpy(buf, "remote:");
+ unsigned pf = strlen(PREFIX);
+ unsigned sf = strlen(SUFFIX);
+ char buf[pf + LARGE_PACKET_MAX + sf + 1];
+ memcpy(buf, PREFIX, pf);
while (1) {
int band, len;
- len = packet_read_line(in_stream, buf+7, LARGE_PACKET_MAX);
+ len = packet_read_line(in_stream, buf + pf, LARGE_PACKET_MAX);
if (len == 0)
break;
if (len < 1) {
safe_write(err, buf, len);
return SIDEBAND_PROTOCOL_ERROR;
}
- band = buf[7] & 0xff;
+ band = buf[pf] & 0xff;
len--;
switch (band) {
case 3:
- buf[7] = ' ';
- buf[8+len] = '\n';
- safe_write(err, buf, 8+len+1);
+ buf[pf] = ' ';
+ buf[pf+1+len] = '\n';
+ safe_write(err, buf, pf+1+len+1);
return SIDEBAND_REMOTE_ERROR;
case 2:
- buf[7] = ' ';
- len += 8;
+ buf[pf] = ' ';
+ len += pf+1;
while (1) {
- int brk = 8;
+ int brk = pf+1;
+
+ /* Break the buffer into separate lines. */
while (brk < len) {
brk++;
if (buf[brk-1] == '\n' ||
buf[brk-1] == '\r')
break;
}
- safe_write(err, buf, brk);
+
+ /*
+ * Let's insert a suffix to clear the end
+ * of the screen line, but only if current
+ * line data actually contains something.
+ */
+ if (brk > pf+1 + 1) {
+ char save[sf];
+ memcpy(save, buf + brk, sf);
+ buf[brk + sf - 1] = buf[brk - 1];
+ memcpy(buf + brk - 1, SUFFIX, sf);
+ safe_write(err, buf, brk + sf);
+ memcpy(buf + brk, save, sf);
+ } else
+ safe_write(err, buf, brk);
+
if (brk < len) {
- memmove(buf + 8, buf + brk, len - brk);
- len = len - brk + 8;
+ memmove(buf + pf+1, buf + brk, len - brk);
+ len = len - brk + pf+1;
} else
break;
}
continue;
case 1:
- safe_write(out, buf+8, len);
+ safe_write(out, buf + pf+1, len);
continue;
default:
len = sprintf(buf,
strbuf_setlen(sb, sb->len + len);
}
+void strbuf_adddup(struct strbuf *sb, size_t pos, size_t len)
+{
+ strbuf_grow(sb, len);
+ memcpy(sb->buf + sb->len, sb->buf + pos, len);
+ strbuf_setlen(sb, sb->len + len);
+}
+
void strbuf_addf(struct strbuf *sb, const char *fmt, ...)
{
int len;
va_list ap;
+ if (!strbuf_avail(sb))
+ strbuf_grow(sb, 64);
va_start(ap, fmt);
len = vsnprintf(sb->buf + sb->len, sb->alloc - sb->len, fmt, ap);
va_end(ap);
- if (len < 0) {
- len = 0;
- }
+ if (len < 0)
+ die("your vsnprintf is broken");
if (len > strbuf_avail(sb)) {
strbuf_grow(sb, len);
va_start(ap, fmt);
strbuf_setlen(sb, sb->len + len);
}
+void strbuf_expand(struct strbuf *sb, const char *format,
+ const char **placeholders, expand_fn_t fn, void *context)
+{
+ for (;;) {
+ const char *percent, **p;
+
+ percent = strchrnul(format, '%');
+ strbuf_add(sb, format, percent - format);
+ if (!*percent)
+ break;
+ format = percent + 1;
+
+ for (p = placeholders; *p; p++) {
+ if (!prefixcmp(format, *p))
+ break;
+ }
+ if (*p) {
+ fn(sb, *p, context);
+ format += strlen(*p);
+ } else
+ strbuf_addch(sb, '%');
+ }
+}
+
size_t strbuf_fread(struct strbuf *sb, size_t size, FILE *f)
{
size_t res;
static inline void strbuf_addbuf(struct strbuf *sb, struct strbuf *sb2) {
strbuf_add(sb, sb2->buf, sb2->len);
}
+extern void strbuf_adddup(struct strbuf *sb, size_t pos, size_t len);
+
+typedef void (*expand_fn_t) (struct strbuf *sb, const char *placeholder, void *context);
+extern void strbuf_expand(struct strbuf *sb, const char *format, const char **placeholders, expand_fn_t fn, void *context);
__attribute__((format(printf,2,3)))
extern void strbuf_addf(struct strbuf *sb, const char *fmt, ...);
-s, --string <string>
get a string
--string2 <str> get another string
+ --st <st> get another string (pervert ordering)
EOF
test $? != 129
'
+cat > expect << EOF
+boolean: 0
+integer: 0
+string: 123
+EOF
+
+test_expect_success 'non ambiguous option (after two options it abbreviates)' '
+ test-parse-options --st 123 > output 2> output.err &&
+ test ! -s output.err &&
+ git diff expect output
+'
+
test_done
echo $h_TEST >.git/MERGE_HEAD &&
GIT_AUTHOR_DATE="2005-05-26 23:45" \
GIT_COMMITTER_DATE="2005-05-26 23:45" git-commit -F M &&
- h_MERGED=$(git rev-parse --verify HEAD)
+ h_MERGED=$(git rev-parse --verify HEAD) &&
rm -f M'
cat >expect <<EOF
git merge -s recursive "reference merge" HEAD master
'
+PRE_REBASE=$(git rev-parse test-rebase)
test_expect_success rebase '
git checkout test-rebase &&
- git rebase --merge master
+ GIT_TRACE=1 git rebase --merge master
+'
+
+test_expect_success 'test-rebase@{1} is pre rebase' '
+ test $PRE_REBASE = $(git rev-parse test-rebase@{1})
'
test_expect_success 'merge and rebase should match' '
git reset --hard HEAD &&
git rebase --skip
'
+
+test_expect_success 'rebase moves back to skip-reference' '
+ test refs/heads/skip-reference = $(git symbolic-ref HEAD) &&
+ git branch post-rebase &&
+ git reset --hard pre-rebase &&
+ ! git rebase master &&
+ echo "hello" > hello &&
+ git add hello &&
+ git rebase --continue &&
+ test refs/heads/skip-reference = $(git symbolic-ref HEAD) &&
+ git reset --hard post-rebase
+'
+
test_expect_success 'checkout skip-merge' 'git checkout -f skip-merge'
test_expect_failure 'rebase with --merge' 'git rebase --merge master'
test_expect_success 'merge and reference trees equal' \
'test -z "`git diff-tree skip-merge skip-reference`"'
+test_expect_success 'moved back to branch correctly' '
+ test refs/heads/skip-merge = $(git symbolic-ref HEAD)
+'
+
test_debug 'gitk --all & sleep 1'
test_done
diff -u expect .git/.dotest-merge/patch &&
diff -u expect2 file1 &&
test 4 = $(grep -v "^#" < .git/.dotest-merge/done | wc -l) &&
- test 0 = $(grep -v "^#" < .git/.dotest-merge/todo | wc -l)
+ test 0 = $(grep -v "^#" < .git/.dotest-merge/git-rebase-todo | wc -l)
'
test_expect_success 'abort' '
--- /dev/null
+#!/bin/sh
+#
+# Copyright (c) 2006 Brian C Gernhardt
+#
+
+test_description='Format-patch numbering options'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+
+ echo A > file &&
+ git add file &&
+ git commit -m First &&
+
+ echo B >> file &&
+ git commit -a -m Second &&
+
+ echo C >> file &&
+ git commit -a -m Third
+
+'
+
+# Each of these gets used multiple times.
+
+test_num_no_numbered() {
+ cnt=$(grep "^Subject: \[PATCH\]" $1 | wc -l) &&
+ test $cnt = $2
+}
+
+test_single_no_numbered() {
+ test_num_no_numbered $1 1
+}
+
+test_no_numbered() {
+ test_num_no_numbered $1 2
+}
+
+test_single_numbered() {
+ grep "^Subject: \[PATCH 1/1\]" $1
+}
+
+test_numbered() {
+ grep "^Subject: \[PATCH 1/2\]" $1 &&
+ grep "^Subject: \[PATCH 2/2\]" $1
+}
+
+test_expect_success 'Default: no numbered' '
+
+ git format-patch --stdout HEAD~2 >patch0 &&
+ test_no_numbered patch0
+
+'
+
+test_expect_success 'Use --numbered' '
+
+ git format-patch --numbered --stdout HEAD~2 >patch1 &&
+ test_numbered patch1
+
+'
+
+test_expect_success 'format.numbered = true' '
+
+ git config format.numbered true &&
+ git format-patch --stdout HEAD~2 >patch2 &&
+ test_numbered patch2
+
+'
+
+test_expect_success 'format.numbered && single patch' '
+
+ git format-patch --stdout HEAD^ > patch3 &&
+ test_single_numbered patch3
+
+'
+
+test_expect_success 'format.numbered && --no-numbered' '
+
+ git format-patch --no-numbered --stdout HEAD~2 >patch4 &&
+ test_no_numbered patch4
+
+'
+
+test_expect_success 'format.numbered = auto' '
+
+ git config format.numbered auto
+ git format-patch --stdout HEAD~2 > patch5 &&
+ test_numbered patch5
+
+'
+
+test_expect_success 'format.numbered = auto && single patch' '
+
+ git format-patch --stdout HEAD^ > patch6 &&
+ test_single_no_numbered patch6
+
+'
+
+test_expect_success 'format.numbered = auto && --no-numbered' '
+
+ git format-patch --no-numbered --stdout HEAD~2 > patch7 &&
+ test_no_numbered patch7
+
+'
+
+test_done
--- /dev/null
+#!/bin/sh
+
+test_description='errors in upload-pack'
+
+. ./test-lib.sh
+
+D=`pwd`
+
+corrupt_repo () {
+ object_sha1=$(git rev-parse "$1") &&
+ ob=$(expr "$object_sha1" : "\(..\)") &&
+ ject=$(expr "$object_sha1" : "..\(..*\)") &&
+ rm -f ".git/objects/$ob/$ject"
+}
+
+test_expect_success 'setup and corrupt repository' '
+
+ echo file >file &&
+ git add file &&
+ git rev-parse :file &&
+ git commit -a -m original &&
+ test_tick &&
+ echo changed >file &&
+ git commit -a -m changed &&
+ corrupt_repo HEAD:file
+
+'
+
+test_expect_failure 'fsck fails' '
+
+ git fsck
+'
+
+test_expect_success 'upload-pack fails due to error in pack-objects' '
+
+ ! echo "0032want $(git rev-parse HEAD)
+00000009done
+0000" | git-upload-pack . > /dev/null 2> output.err &&
+ grep "pack-objects died" output.err
+'
+
+test_expect_success 'corrupt repo differently' '
+
+ git hash-object -w file &&
+ corrupt_repo HEAD^^{tree}
+
+'
+
+test_expect_failure 'fsck fails' '
+
+ git fsck
+'
+test_expect_success 'upload-pack fails due to error in rev-list' '
+
+ ! echo "0032want $(git rev-parse HEAD)
+00000009done
+0000" | git-upload-pack . > /dev/null 2> output.err &&
+ grep "waitpid (async) failed" output.err
+'
+
+test_expect_success 'create empty repository' '
+
+ mkdir foo &&
+ cd foo &&
+ git init
+
+'
+
+test_expect_failure 'fetch fails' '
+
+ git fetch .. master
+
+'
+
+test_done
git diff expected actual
'
+cat >expected <<\EOF
+refs/heads/master
+refs/tags/testtag
+EOF
+
+test_expect_success 'Verify ascending sort' '
+ git-for-each-ref --format="%(refname)" --sort=refname >actual &&
+ git diff expected actual
+'
+
+
+cat >expected <<\EOF
+refs/tags/testtag
+refs/heads/master
+EOF
+
+test_expect_success 'Verify descending sort' '
+ git-for-each-ref --format="%(refname)" --sort=-refname >actual &&
+ git diff expected actual
+'
+
+
test_done
check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc
'
+test_expect_success 'reset --soft with unmerged index should fail' '
+ touch .git/MERGE_HEAD &&
+ echo "100644 44c5b5884550c17758737edcced463447b91d42b 1 un" |
+ git update-index --index-info &&
+ ! git reset --soft HEAD &&
+ rm .git/MERGE_HEAD &&
+ git rm --cached -- un
+'
+
test_expect_success \
'giving paths with options different than --mixed should fail' '
! git reset --soft -- first &&
git diff-index --cached --exit-code HEAD
'
+cat > expect << EOF
+file2: needs update
+EOF
+
+test_expect_success '--mixed refreshes the index' '
+ echo 123 >> file2 &&
+ git reset --mixed HEAD > output &&
+ git diff --exit-code expect output
+'
+
test_done
test_expect_success "checkout -m with dirty tree" '
git checkout -f master &&
- git clean &&
+ git clean -f &&
fill 0 1 2 3 4 5 6 7 8 >one &&
git checkout -m side &&
test_expect_success "checkout -m with dirty tree, renamed" '
- git checkout -f master && git clean &&
+ git checkout -f master && git clean -f &&
fill 1 2 3 4 5 7 8 >one &&
if git checkout renamer
test_expect_success 'checkout -m with merge conflict' '
- git checkout -f master && git clean &&
+ git checkout -f master && git clean -f &&
fill 1 T 3 4 5 6 S 8 >one &&
if git checkout renamer
test_expect_success 'checkout to detach HEAD' '
- git checkout -f renamer && git clean &&
+ git checkout -f renamer && git clean -f &&
git checkout renamer^ &&
H=$(git rev-parse --verify HEAD) &&
M=$(git show-ref -s --verify refs/heads/master) &&
test_expect_success 'checkout to detach HEAD with branchname^' '
- git checkout -f master && git clean &&
+ git checkout -f master && git clean -f &&
git checkout renamer^ &&
H=$(git rev-parse --verify HEAD) &&
M=$(git show-ref -s --verify refs/heads/master) &&
test_expect_success 'checkout to detach HEAD with HEAD^0' '
- git checkout -f master && git clean &&
+ git checkout -f master && git clean -f &&
git checkout HEAD^0 &&
H=$(git rev-parse --verify HEAD) &&
M=$(git show-ref -s --verify refs/heads/master) &&
. ./test-lib.sh
+git config clean.requireForce no
+
test_expect_success 'setup' '
mkdir -p src &&
'
+test_expect_success 'clean.requireForce defaults to true' '
+
+ git config --unset clean.requireForce &&
+ ! git-clean
+
+'
+
test_expect_success 'clean.requireForce' '
git config clean.requireForce true &&
'
+test_expect_success 'core.excludesfile' '
+
+ echo excludes >excludes &&
+ echo included >included &&
+ git config core.excludesfile excludes &&
+ output=$(git clean -n excludes included 2>&1) &&
+ expr "$output" : ".*included" >/dev/null &&
+ ! expr "$output" : ".*excludes" >/dev/null
+
+'
+
test_done
'
+author="The Real Author <someguy@his.email.org>"
+test_expect_success 'amend commit to fix author' '
+
+ oldtick=$GIT_AUTHOR_DATE &&
+ test_tick &&
+ git reset --hard &&
+ git cat-file -p HEAD |
+ sed -e "s/author.*/author $author $oldtick/" \
+ -e "s/^\(committer.*> \).*$/\1$GIT_COMMITTER_DATE/" > \
+ expected &&
+ git commit --amend --author="$author" &&
+ git cat-file -p HEAD > current &&
+ diff expected current
+
+'
+
+test_expect_success 'sign off (1)' '
+
+ echo 1 >positive &&
+ git add positive &&
+ git commit -s -m "thank you" &&
+ git cat-file commit HEAD | sed -e "1,/^\$/d" >actual &&
+ (
+ echo thank you
+ echo
+ git var GIT_COMMITTER_IDENT |
+ sed -e "s/>.*/>/" -e "s/^/Signed-off-by: /"
+ ) >expected &&
+ diff -u expected actual
+
+'
+
+test_expect_success 'sign off (2)' '
+
+ echo 2 >positive &&
+ git add positive &&
+ existing="Signed-off-by: Watch This <watchthis@example.com>" &&
+ git commit -s -m "thank you
+
+$existing" &&
+ git cat-file commit HEAD | sed -e "1,/^\$/d" >actual &&
+ (
+ echo thank you
+ echo
+ echo $existing
+ git var GIT_COMMITTER_IDENT |
+ sed -e "s/>.*/>/" -e "s/^/Signed-off-by: /"
+ ) >expected &&
+ diff -u expected actual
+
+'
+
+test_expect_success 'multiple -m' '
+
+ >negative &&
+ git add negative &&
+ git commit -m "one" -m "two" -m "three" &&
+ git cat-file commit HEAD | sed -e "1,/^\$/d" >actual &&
+ (
+ echo one
+ echo
+ echo two
+ echo
+ echo three
+ ) >expected &&
+ diff -u expected actual
+
+'
+
test_done
test x\"\`sed -n -e 61p < file\`\" = x6611
"
+test_expect_failure 'attempt to dcommit with a dirty index' '
+ echo foo >>file &&
+ git add file &&
+ git svn dcommit
+'
+
test_done
--- /dev/null
+#!/bin/sh
+#
+# Copyright (c) 2007 Eric Wong
+#
+
+test_description='git-svn funky branch names'
+. ./lib-git-svn.sh
+
+test_expect_success 'setup svnrepo' "
+ mkdir project project/trunk project/branches project/tags &&
+ echo foo > project/trunk/foo &&
+ svn import -m '$test_description' project \"$svnrepo/pr ject\" &&
+ rm -rf project &&
+ svn cp -m 'fun' \"$svnrepo/pr ject/trunk\" \
+ \"$svnrepo/pr ject/branches/fun plugin\" &&
+ svn cp -m 'more fun!' \"$svnrepo/pr ject/branches/fun plugin\" \
+ \"$svnrepo/pr ject/branches/more fun plugin!\" &&
+ start_httpd
+ "
+
+test_expect_success 'test clone with funky branch names' "
+ git svn clone -s \"$svnrepo/pr ject\" project &&
+ cd project &&
+ git rev-parse 'refs/remotes/fun%20plugin' &&
+ git rev-parse 'refs/remotes/more%20fun%20plugin!' &&
+ cd ..
+ "
+
+test_expect_success 'test dcommit to funky branch' "
+ cd project &&
+ git reset --hard 'refs/remotes/more%20fun%20plugin!' &&
+ echo hello >> foo &&
+ git commit -m 'hello' -- foo &&
+ git svn dcommit &&
+ cd ..
+ "
+
+stop_httpd
+
+test_done
tput sgr0 >/dev/null 2>&1 &&
color=t
-test "${test_description}" != "" ||
-error "Test script did not set test_description."
-
while test "$#" -ne 0
do
case "$1" in
-i|--i|--im|--imm|--imme|--immed|--immedi|--immedia|--immediat|--immediate)
immediate=t; shift ;;
-h|--h|--he|--hel|--help)
- echo "$test_description"
- exit 0 ;;
+ help=t; shift ;;
-v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose)
verbose=t; shift ;;
-q|--q|--qu|--qui|--quie|--quiet)
say_color info "$*"
}
+test "${test_description}" != "" ||
+error "Test script did not set test_description."
+
+if test "$help" = "t"
+then
+ echo "$test_description"
+ exit 0
+fi
+
exec 5>&1
if test "$verbose" = "t"
then
# hooks.allowunannotated
# This boolean sets whether unannotated tags will be allowed into the
# repository. By default they won't be.
+# hooks.allowdeletetag
+# This boolean sets whether deleting tags will be allowed in the
+# repository. By default they won't be.
+# hooks.allowdeletebranch
+# This boolean sets whether deleting branches will be allowed in the
+# repository. By default they won't be.
#
# --- Command line
# --- Config
allowunannotated=$(git-repo-config --bool hooks.allowunannotated)
+allowdeletebranch=$(git-repo-config --bool hooks.allowdeletebranch)
+allowdeletetag=$(git-repo-config --bool hooks.allowdeletetag)
# check for no description
-projectdesc=$(sed -e '1p' "$GIT_DIR/description")
-if [ -z "$projectdesc" -o "$projectdesc" = "Unnamed repository; edit this file to name it for gitweb" ]; then
+projectdesc=$(sed -e '1q' "$GIT_DIR/description")
+if [ -z "$projectdesc" -o "$projectdesc" = "Unnamed repository; edit this file to name it for gitweb." ]; then
echo "*** Project description file hasn't been set" >&2
exit 1
fi
# --- Check types
-# if $newrev is 0000...0000, it's a commit to delete a branch
+# if $newrev is 0000...0000, it's a commit to delete a ref.
if [ "$newrev" = "0000000000000000000000000000000000000000" ]; then
- newrev_type=commit
+ newrev_type=delete
else
newrev_type=$(git-cat-file -t $newrev)
fi
exit 1
fi
;;
+ refs/tags/*,delete)
+ # delete tag
+ if [ "$allowdeletetag" != "true" ]; then
+ echo "*** Deleting a tag is not allowed in this repository" >&2
+ exit 1
+ fi
+ ;;
refs/tags/*,tag)
# annotated tag
;;
refs/heads/*,commit)
# branch
;;
+ refs/heads/*,delete)
+ # delete branch
+ if [ "$allowdeletebranch" != "true" ]; then
+ echo "*** Deleting a branch is not allowed in this repository" >&2
+ exit 1
+ fi
+ ;;
refs/remotes/*,commit)
# tracking branch
;;
+ refs/remotes/*,delete)
+ # delete tracking branch
+ if [ "$allowdeletebranch" != "true" ]; then
+ echo "*** Deleting a tracking branch is not allowed in this repository" >&2
+ exit 1
+ fi
+ ;;
*)
# Anything else (is there anything else?)
echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2
OPT_GROUP("string options"),
OPT_STRING('s', "string", &string, "string", "get a string"),
OPT_STRING(0, "string2", &string, "str", "get another string"),
+ OPT_STRING(0, "st", &string, "st", "get another string (pervert ordering)"),
OPT_END(),
};
int i;
if (!fd)
return;
- strbuf_init(&buf, 0);
+ strbuf_init(&buf, 64);
va_start(ap, fmt);
len = vsnprintf(buf.buf, strbuf_avail(&buf), fmt, ap);
va_end(ap);
if (!fd)
return;
- strbuf_init(&buf, 0);
+ strbuf_init(&buf, 64);
va_start(ap, fmt);
len = vsnprintf(buf.buf, strbuf_avail(&buf), fmt, ap);
va_end(ap);
}
#ifndef NO_CURL
-static int curl_transport_push(struct transport *transport, int refspec_nr, const char **refspec, int flags) {
+static int curl_transport_push(struct transport *transport, int refspec_nr, const char **refspec, int flags)
+{
const char **argv;
int argc;
int err;
- argv = xmalloc((refspec_nr + 11) * sizeof(char *));
+ argv = xmalloc((refspec_nr + 12) * sizeof(char *));
argv[0] = "http-push";
argc = 1;
if (flags & TRANSPORT_PUSH_ALL)
argv[argc++] = "--force";
if (flags & TRANSPORT_PUSH_DRY_RUN)
argv[argc++] = "--dry-run";
+ if (flags & TRANSPORT_PUSH_VERBOSE)
+ argv[argc++] = "--verbose";
argv[argc++] = transport->url;
while (refspec_nr--)
argv[argc++] = *refspec++;
return 0;
}
-static int git_transport_push(struct transport *transport, int refspec_nr, const char **refspec, int flags) {
+static int git_transport_push(struct transport *transport, int refspec_nr, const char **refspec, int flags)
+{
struct git_transport_data *data = transport->data;
const char **argv;
char *rem;
int argc;
int err;
- argv = xmalloc((refspec_nr + 11) * sizeof(char *));
+ argv = xmalloc((refspec_nr + 12) * sizeof(char *));
argv[0] = "send-pack";
argc = 1;
if (flags & TRANSPORT_PUSH_ALL)
argv[argc++] = "--force";
if (flags & TRANSPORT_PUSH_DRY_RUN)
argv[argc++] = "--dry-run";
+ if (flags & TRANSPORT_PUSH_VERBOSE)
+ argv[argc++] = "--verbose";
if (data->receivepack) {
char *rp = xmalloc(strlen(data->receivepack) + 16);
sprintf(rp, "--receive-pack=%s", data->receivepack);
#define TRANSPORT_PUSH_ALL 1
#define TRANSPORT_PUSH_FORCE 2
#define TRANSPORT_PUSH_DRY_RUN 4
+#define TRANSPORT_PUSH_VERBOSE 8
/* Returns a transport suitable for the url */
struct transport *transport_get(struct remote *, const char *);
char abort_msg[] = "aborting due to possible repository "
"corruption on the remote side.";
int buffered = -1;
+ ssize_t sz;
const char *argv[10];
int arg = 0;
pack_objects.git_cmd = 1;
pack_objects.argv = argv;
- if (start_command(&pack_objects)) {
- /* daemon sets things up to ignore TERM */
- kill(rev_list.pid, SIGKILL);
+ if (start_command(&pack_objects))
die("git-upload-pack: unable to fork git-pack-objects");
- }
/* We read from pack_objects.err to capture stderr output for
* progress bar, and pack_objects.out to capture the pack data.
*/
while (1) {
- const char *who;
struct pollfd pfd[2];
- pid_t pid;
- int status;
- ssize_t sz;
int pe, pu, pollsize;
reset_timeout();
pollsize++;
}
- if (pollsize) {
- if (poll(pfd, pollsize, -1) < 0) {
- if (errno != EINTR) {
- error("poll failed, resuming: %s",
- strerror(errno));
- sleep(1);
- }
- continue;
- }
- if (0 <= pu && (pfd[pu].revents & (POLLIN|POLLHUP))) {
- /* Data ready; we keep the last byte
- * to ourselves in case we detect
- * broken rev-list, so that we can
- * leave the stream corrupted. This
- * is unfortunate -- unpack-objects
- * would happily accept a valid pack
- * data with trailing garbage, so
- * appending garbage after we pass all
- * the pack data is not good enough to
- * signal breakage to downstream.
- */
- char *cp = data;
- ssize_t outsz = 0;
- if (0 <= buffered) {
- *cp++ = buffered;
- outsz++;
- }
- sz = xread(pack_objects.out, cp,
- sizeof(data) - outsz);
- if (0 < sz)
- ;
- else if (sz == 0) {
- close(pack_objects.out);
- pack_objects.out = -1;
- }
- else
- goto fail;
- sz += outsz;
- if (1 < sz) {
- buffered = data[sz-1] & 0xFF;
- sz--;
- }
- else
- buffered = -1;
- sz = send_client_data(1, data, sz);
- if (sz < 0)
- goto fail;
- }
- if (0 <= pe && (pfd[pe].revents & (POLLIN|POLLHUP))) {
- /* Status ready; we ship that in the side-band
- * or dump to the standard error.
- */
- sz = xread(pack_objects.err, progress,
- sizeof(progress));
- if (0 < sz)
- send_client_data(2, progress, sz);
- else if (sz == 0) {
- close(pack_objects.err);
- pack_objects.err = -1;
- }
- else
- goto fail;
+ if (!pollsize)
+ break;
+
+ if (poll(pfd, pollsize, -1) < 0) {
+ if (errno != EINTR) {
+ error("poll failed, resuming: %s",
+ strerror(errno));
+ sleep(1);
}
+ continue;
}
-
- /* See if the children are still there */
- if (rev_list.pid || pack_objects.pid) {
- pid = waitpid(-1, &status, WNOHANG);
- if (!pid)
- continue;
- who = ((pid == rev_list.pid) ? "git-rev-list" :
- (pid == pack_objects.pid) ? "git-pack-objects" :
- NULL);
- if (!who) {
- if (pid < 0) {
- error("git-upload-pack: %s",
- strerror(errno));
- goto fail;
- }
- error("git-upload-pack: we weren't "
- "waiting for %d", pid);
- continue;
+ if (0 <= pu && (pfd[pu].revents & (POLLIN|POLLHUP))) {
+ /* Data ready; we keep the last byte to ourselves
+ * in case we detect broken rev-list, so that we
+ * can leave the stream corrupted. This is
+ * unfortunate -- unpack-objects would happily
+ * accept a valid packdata with trailing garbage,
+ * so appending garbage after we pass all the
+ * pack data is not good enough to signal
+ * breakage to downstream.
+ */
+ char *cp = data;
+ ssize_t outsz = 0;
+ if (0 <= buffered) {
+ *cp++ = buffered;
+ outsz++;
+ }
+ sz = xread(pack_objects.out, cp,
+ sizeof(data) - outsz);
+ if (0 < sz)
+ ;
+ else if (sz == 0) {
+ close(pack_objects.out);
+ pack_objects.out = -1;
}
- if (!WIFEXITED(status) || WEXITSTATUS(status) > 0) {
- error("git-upload-pack: %s died with error.",
- who);
+ else
goto fail;
+ sz += outsz;
+ if (1 < sz) {
+ buffered = data[sz-1] & 0xFF;
+ sz--;
}
- if (pid == rev_list.pid)
- rev_list.pid = 0;
- if (pid == pack_objects.pid)
- pack_objects.pid = 0;
- if (rev_list.pid || pack_objects.pid)
- continue;
- }
-
- /* both died happily */
- if (pollsize)
- continue;
-
- /* flush the data */
- if (0 <= buffered) {
- data[0] = buffered;
- sz = send_client_data(1, data, 1);
+ else
+ buffered = -1;
+ sz = send_client_data(1, data, sz);
if (sz < 0)
goto fail;
- fprintf(stderr, "flushed.\n");
}
- if (use_sideband)
- packet_flush(1);
- return;
+ if (0 <= pe && (pfd[pe].revents & (POLLIN|POLLHUP))) {
+ /* Status ready; we ship that in the side-band
+ * or dump to the standard error.
+ */
+ sz = xread(pack_objects.err, progress,
+ sizeof(progress));
+ if (0 < sz)
+ send_client_data(2, progress, sz);
+ else if (sz == 0) {
+ close(pack_objects.err);
+ pack_objects.err = -1;
+ }
+ else
+ goto fail;
+ }
+ }
+
+ if (finish_command(&pack_objects)) {
+ error("git-upload-pack: git-pack-objects died with error.");
+ goto fail;
+ }
+ if (finish_async(&rev_list))
+ goto fail; /* error was already reported */
+
+ /* flush the data */
+ if (0 <= buffered) {
+ data[0] = buffered;
+ sz = send_client_data(1, data, 1);
+ if (sz < 0)
+ goto fail;
+ fprintf(stderr, "flushed.\n");
}
+ if (use_sideband)
+ packet_flush(1);
+ return;
+
fail:
- if (pack_objects.pid)
- kill(pack_objects.pid, SIGKILL);
- if (rev_list.pid)
- kill(rev_list.pid, SIGKILL);
send_client_data(3, abort_msg, sizeof(abort_msg));
die("git-upload-pack: %s", abort_msg);
}
static void report(const char *prefix, const char *err, va_list params)
{
- fputs(prefix, stderr);
- vfprintf(stderr, err, params);
- fputs("\n", stderr);
+ char msg[256];
+ vsnprintf(msg, sizeof(msg), err, params);
+ fprintf(stderr, "%s%s\n", prefix, msg);
}
static NORETURN void usage_builtin(const char *err)
};
/* auxiliary function for binary search in interval table */
-static int bisearch(ucs_char_t ucs, const struct interval *table, int max) {
+static int bisearch(ucs_char_t ucs, const struct interval *table, int max)
+{
int min = 0;
int mid;