* Re: People unaware of the importance of "git gc"?
From: Shawn O. Pearce @ 2007-09-06 2:52 UTC (permalink / raw)
To: Russ Dill; +Cc: git, Junio C Hamano, Nicolas Pitre
In-Reply-To: <loom.20070906T044017-727@post.gmane.org>
Russ Dill <Russ.Dill@gmail.com> wrote:
> > Ok, how about doing something like this?
>
> git add? merge? rebase? No, I have a sneakier place to invoke gc.
>
> Whenever $EDITOR gets invoked. Heck, whenever git is waiting for any user input,
> do some gc in the background, it'd just have to be incremental so that we could
> pick up where we left off.
Heh. That is a really good idea. I've been thinking about doing
some automatic generational style GC type repacking controls in
git-gui, and doing them when git-gui is sitting idle and has not
been used in the past couple of minutes.
This is along the same vein of thought. I like it. Often it
takes me a while to come up with a good commit message even if
I am using command line commit.
But git-rebase/git-am can cause a huge number of objects to be
created, especially if you are pushing a large stack of patches
around. So it may still be a good idea to trigger `gc --auto`
at the end of those operations.
> Similarly, you could mix it in with git pull/push so that while we are waiting
> on the network, we can do some packing.
Here's a better thought:
If we are pushing somewhere, and the push size is "large-ish" and
we aren't pushing a thin pack (its currently considered not nice
to the remote end so it doesn't happen by default) and the objects
we are packing are mostly all loose maybe we should also save a
copy of that packfile locally, then prune *only* those loose objects
back.
Not every git user pushes their work. But many do. And those
that push usually will do so in bursts, are already expecting to
wait for the network latency, and usually are pushing the majority
of the things that are loose. Such users will probably never see
the `gc --auto` trip in places like commit/am/merge as they would
already be clearing their ODB with the push.
--
Shawn.
^ permalink raw reply
* Re: People unaware of the importance of "git gc"?
From: Steven Grimm @ 2007-09-06 2:49 UTC (permalink / raw)
To: Shawn O. Pearce
Cc: Junio C Hamano, Nicolas Pitre, Nix, Linus Torvalds,
Git Mailing List
In-Reply-To: <20070906024555.GJ18160@spearce.org>
Shawn O. Pearce wrote:
> But this suffers from the same fate if the user sets gc.auto too
> small and doesn't realize that the reason Git is always repacking
> is because over the last 6 months they have been unlucky enough to
> stage the magic number of unreachable blobs into the 17 directory
> and they have *never* run `git gc --prune` because the auto thing
> is working just fine for them and they don't realize they need to
> prune every once in a blue moon.
>
Check the modification times on those files and don't count ones that
are older than the last git-gc run, maybe? That'd take care of the problem.
-Steve
^ permalink raw reply
* Re: People unaware of the importance of "git gc"?
From: Shawn O. Pearce @ 2007-09-06 2:45 UTC (permalink / raw)
To: Junio C Hamano
Cc: Nicolas Pitre, Nix, Steven Grimm, Linus Torvalds,
Git Mailing List
In-Reply-To: <7vr6lcj2zi.fsf@gitster.siamese.dyndns.org>
Junio C Hamano <gitster@pobox.com> wrote:
> Implement git gc --auto
...
Danger... If the user sets `gc.auto` to a low enough value and
they are also unlucky enough to have a few truely unreachable (thus
pruneable) objects in .git/objects/17/ then this is going to run
a bunch of gc work on every commit they make.
I'm actually running into this problem in git-gui. On Windows
it suggests a repack if there is one object in .git/objects/42/.
Some users have been unlucky enough to stage a file, have it
hash into that directory, then restage a different version of it.
The prior one is never considered reachable (it was never committed),
but will now *always* cause git-gui to suggest a repack on every
startup. For all time.
Yea, I need to fix that.
But this suffers from the same fate if the user sets gc.auto too
small and doesn't realize that the reason Git is always repacking
is because over the last 6 months they have been unlucky enough to
stage the magic number of unreachable blobs into the 17 directory
and they have *never* run `git gc --prune` because the auto thing
is working just fine for them and they don't realize they need to
prune every once in a blue moon.
--
Shawn.
^ permalink raw reply
* Re: People unaware of the importance of "git gc"?
From: Russ Dill @ 2007-09-06 2:44 UTC (permalink / raw)
To: git
In-Reply-To: <7vr6lcj2zi.fsf@gitster.siamese.dyndns.org>
> Ok, how about doing something like this?
>
git add? merge? rebase? No, I have a sneakier place to invoke gc.
Whenever $EDITOR gets invoked. Heck, whenever git is waiting for any user input,
do some gc in the background, it'd just have to be incremental so that we could
pick up where we left off.
Similarly, you could mix it in with git pull/push so that while we are waiting
on the network, we can do some packing.
Course, this wouldn't work for all repositories.
^ permalink raw reply
* Re: Invoke "git gc --auto" from commit, merge, am and rebase.
From: Shawn O. Pearce @ 2007-09-06 2:39 UTC (permalink / raw)
To: Junio C Hamano
Cc: Nicolas Pitre, Nix, Steven Grimm, Linus Torvalds,
Git Mailing List
In-Reply-To: <7vmyw0hixs.fsf_-_@gitster.siamese.dyndns.org>
Junio C Hamano <gitster@pobox.com> wrote:
> The point of auto gc is to pack new objects created in loose
> format, so a good rule of thumb is where we do update-ref after
> creating a new commit.
...
> git-am.sh | 2 ++
> git-commit.sh | 1 +
> git-merge.sh | 1 +
> git-rebase--interactive.sh | 2 ++
> 4 files changed, 6 insertions(+), 0 deletions(-)
...
> diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
> index abc2b1c..8258b7a 100755
> --- a/git-rebase--interactive.sh
> +++ b/git-rebase--interactive.sh
> @@ -307,6 +307,8 @@ do_next () {
> rm -rf "$DOTEST" &&
> warn "Successfully rebased and updated $HEADNAME."
>
> + git gc --auto
> +
> exit
> }
Why bother with git-rebase--interactive.sh? It calls two tools,
git-cherry-pick (which calls git-commit) and git-commit to do its
per-commit dirty work. So on every step of `git rebase -i` we are
now running `git gc --auto`. No need to also run it at the end.
Note this is also true of `git rebase -m` as that uses the wonderful
feature of `git commit -C $oldid` per commit to make the new commit.
--
Shawn.
^ permalink raw reply
* Re: [RFC] Convert builin-mailinfo.c to use The Better String Library.
From: Miles Bader @ 2007-09-06 2:30 UTC (permalink / raw)
To: Matthieu Moy
Cc: Kristian Høgsberg, Lukas Sandström, Git Mailing List,
Junio C Hamano
In-Reply-To: <vpq642pkoln.fsf@bauges.imag.fr>
Matthieu Moy <Matthieu.Moy@imag.fr> writes:
> and if people decide that git needs a non-trivial string library,
> writting/testing more code in strbuf.c would probably be more work
> than just reading what bsstring code does to become familiar enough
> with it to even be able to maintain it later.
>From what I've seen (by perusing the bstring website), bstring is kind
of ugly though....
-Miles
--
"Suppose we've chosen the wrong god. Every time we go to church we're
just making him madder and madder." -- Homer Simpson
^ permalink raw reply
* Re: [PATCH 1/3] git-gui/Makefile: Replace libdir with gitgui_libdir
From: Shawn O. Pearce @ 2007-09-06 2:32 UTC (permalink / raw)
To: Dmitry V. Levin; +Cc: Git Mailing List, Junio C Hamano
In-Reply-To: <20070905232153.GA331@nomad.office.altlinux.org>
"Dmitry V. Levin" <ldv@altlinux.org> wrote:
> On GNU/Linux, libdir is used to mean "/usr/lib or /usr/lib64"
> depending on architecture. Different libdir meaning breaks
> idiomatic expressions like rpm specfile "make libdir=%_libdir".
>
> Signed-off-by: Dmitry V. Levin <ldv@altlinux.org>
> ---
> git-gui/Makefile | 16 ++++++++--------
> 1 files changed, 8 insertions(+), 8 deletions(-)
Although I could apply this with `am -3` I'm NACK'ing this right
now because...
> diff --git a/git-gui/Makefile b/git-gui/Makefile
> index 1bac6fe..f143b2c 100644
> --- a/git-gui/Makefile
> +++ b/git-gui/Makefile
> @@ -76,8 +76,8 @@ SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))
> TCL_PATH_SQ = $(subst ','\'',$(TCL_PATH))
> TCLTK_PATH_SQ = $(subst ','\'',$(TCLTK_PATH))
>
> -libdir ?= $(sharedir)/git-gui/lib
> -libdir_SQ = $(subst ','\'',$(libdir))
> +gitgui_libdir ?= $(sharedir)/git-gui/lib
> +gitgui_libdir_SQ = $(subst ','\'',$(gitgui_libdir))
git-gui is its own project with its own Makefile. Junio includes
it in git.git to help widen its audience, and because it is quite
portable and easy for him to include. In the future git-gui will
become a proper subproject of git.git.
If you want to define libdir in git's toplevel Makefile *and*
that definition is being exported down into git-gui's Makefile
then I think git's toplevel Makefile should arrange for it to
not export libdir down into git-gui.
Of course one has to wonder if that is even possible in GNU make.
I'm not a GNU make expert so I don't really know.
--
Shawn.
^ permalink raw reply
* Re: [PATCH 1/2] git-commit: Disallow unchanged tree in non-merge mode
From: Shawn O. Pearce @ 2007-09-06 2:25 UTC (permalink / raw)
To: Dmitry V. Levin; +Cc: Git Mailing List, Junio C Hamano
In-Reply-To: <20070905234941.GA643@nomad.office.altlinux.org>
"Dmitry V. Levin" <ldv@altlinux.org> wrote:
> Do not commit an unchanged tree in non-merge mode.
A laudable goal. git-gui also does this. Turns out the other
checks within git-gui prevent the user from ever getting that far.
I probably should remove the empty tree check as it costs CPU time
to get the old tree. But I'd rather have the safety check.
> The idea is that amend should not commit an unchanged tree,
> one should just remove the top commit using git-reset instead.
NO. `git commit --amend` is *often* used for fixing the commit
message. Or adding additional detail. Forcing the user to do
a `git reset --soft HEAD^ && git commit --amend` just because
you don't want git-commit to make an "empty commit" (which it
doesn't usually like to do now anyway!) is a major step back
in functionality.
NACK.
> diff --git a/git-commit.sh b/git-commit.sh
> index 1d04f1f..800f96c 100755
> --- a/git-commit.sh
> +++ b/git-commit.sh
> @@ -629,6 +629,16 @@ then
> tree=$(GIT_INDEX_FILE="$TMP_INDEX" git write-tree) &&
> rm -f "$TMP_INDEX"
> fi &&
> + if test -n "$current" -a ! -f "$GIT_DIR/MERGE_HEAD"
> + then
> + current_tree="$(git cat-file commit "$current${amend:+^}" 2>/dev/null |
> + sed -e '/^tree \+/!d' -e 's///' -e q)"
The better way to get the old tree would be this:
current_tree="$(git rev-parse "$current${amend:+^}^{tree}" 2>/dev/null
as it avoids the tool from needing to know about the internal
representation of a commit object. It also avoids an entire
fork+exec of a sed process.
> + if test "$tree" = "$current_tree"
> + then
> + echo >&2 "nothing to commit${amend:+ (use \"git reset HEAD^\" to remove the top commit)}"
That message is a bad idea. Doing a mixed mode reset will also
reset the index, causing the user to lose any changes that had
already been staged. This may actually be difficult for him/her to
recover from if they have used `git add -i` or git-gui to stage only
certain hunks of files, or if their working tree has been further
modified after the commit but they want to go back and amend the
message only of the prior commit.
--
Shawn.
^ permalink raw reply
* Re: Significant performance waste in git-svn and friends
From: Shawn O. Pearce @ 2007-09-06 2:19 UTC (permalink / raw)
To: David Kastrup; +Cc: Junio C Hamano, Mike Hommey, git
In-Reply-To: <85bqcghktl.fsf@lola.goethe.zz>
David Kastrup <dak@gnu.org> wrote:
> Junio C Hamano <gitster@pobox.com> writes:
>
> > Mike Hommey <mh@glandium.org> writes:
> >
> >> The same things obviously apply to git-cvsimport and other scripts
> >> calling git-hash-object a lot.
> >
> > I wonder if letting fast-import handle the object creation is an
> > option, though.
>
> I think it would be saner to give git-hash-object an operation mode
> that makes it usable as a pipe-controlled daemon, so that one needs
> not fork and exec for interning another object. That way, porcelain
> commands could keep one bidirectional pipe (feed object type and
> source and whether to use -w into git-hash-project, receive object id)
> to git-hash-object around until they finish.
Aside from getting the hashes back from fast-import, that's what
fast-import is for. I could also make it disable writing. Hmm.
Junio and I were just talking about making fast-import send the
marks table back out on stdout. This would make it easier for a
frontend process to stream a whole bunch of objects into the process,
then get back all of their SHA-1s. Less context switches and more
parallel operation.
--
Shawn.
^ permalink raw reply
* Re: Significant performance waste in git-svn and friends
From: Shawn O. Pearce @ 2007-09-06 2:16 UTC (permalink / raw)
To: Junio C Hamano; +Cc: Mike Hommey, git
In-Reply-To: <7vd4wwj16d.fsf@gitster.siamese.dyndns.org>
Junio C Hamano <gitster@pobox.com> wrote:
> Mike Hommey <mh@glandium.org> writes:
>
> > The same things obviously apply to git-cvsimport and other scripts
> > calling git-hash-object a lot.
>
> I *obviously* hate this patch, as it makes this Porcelain
> command to be aware of the internal representation too much.
Me too. I think its bad enough that fast-import knows the
internal representation of a packfile. And its plumbing!
> I wonder if letting fast-import handle the object creation is an
> option, though.
I agree. This is *exactly* what fast-import was built for. It
really would be the better tool to use here. Much better than
reinventing the wheel. Its already been invented twice for you
in Git (normal object creation path and fast-import path). No
need to do it a third time.
--
Shawn.
^ permalink raw reply
* [PATCH] Cleanup unnecessary file modifications in t1400-update-ref
From: Shawn O. Pearce @ 2007-09-06 2:15 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git, krh
Kristian Høgsberg pointed out that the two file modifications
we were doing during the 'creating initial files' step are not even
used within the test suite. This was actually confusing as we do
not even need these changes for the tests to pass. All that really
matters here is the specific commit dates are used so that these
appear in the branch's reflog, and that the dates are different so
that the branch will update when asked and the reflog entry is
also updated. There is no need for the file modification.
Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
---
t/t1400-update-ref.sh | 2 --
1 files changed, 0 insertions(+), 2 deletions(-)
diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh
index c4c0dfa..ce045b2 100755
--- a/t/t1400-update-ref.sh
+++ b/t/t1400-update-ref.sh
@@ -198,11 +198,9 @@ test_expect_success \
GIT_AUTHOR_DATE="2005-05-26 23:41" \
GIT_COMMITTER_DATE="2005-05-26 23:41" git-commit -F M -a &&
h_OTHER=$(git rev-parse --verify HEAD) &&
- echo FIXED >F &&
GIT_AUTHOR_DATE="2005-05-26 23:44" \
GIT_COMMITTER_DATE="2005-05-26 23:44" git-commit --amend &&
h_FIXED=$(git rev-parse --verify HEAD) &&
- echo TEST+FIXED >F &&
echo Merged initial commit and a later commit. >M &&
echo $h_TEST >.git/MERGE_HEAD &&
GIT_AUTHOR_DATE="2005-05-26 23:45" \
--
1.5.3.1.840.g0fedbc
^ permalink raw reply related
* Re: builtin commit series sent
From: Shawn O. Pearce @ 2007-09-06 2:09 UTC (permalink / raw)
To: Kristian Høgsberg; +Cc: Git Mailing List
In-Reply-To: <1189039527.20311.24.camel@hinata.boston.redhat.com>
Kristian Hgsberg <krh@redhat.com> wrote:
> There's one test that doesn't look right to me:
> t1400-update-ref.sh. The 'creating initial files' case does a git
> commit --amend and apparently expect to commit the changed contents of
> the file F, even though it hasn't been added to the index. Similarly,
> there's a git commit -F M just below it that does the same thing. I'm
> not sure how they pass with the shell script commit...
Yea. It doesn't actually matter in this test. The following
patch can be applied and the test will still pass:
diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh
index c4c0dfa..ce045b2 100755
--- a/t/t1400-update-ref.sh
+++ b/t/t1400-update-ref.sh
@@ -198,11 +198,9 @@ test_expect_success \
GIT_AUTHOR_DATE="2005-05-26 23:41" \
GIT_COMMITTER_DATE="2005-05-26 23:41" git-commit -F M -a &&
h_OTHER=$(git rev-parse --verify HEAD) &&
- echo FIXED >F &&
GIT_AUTHOR_DATE="2005-05-26 23:44" \
GIT_COMMITTER_DATE="2005-05-26 23:44" git-commit --amend &&
h_FIXED=$(git rev-parse --verify HEAD) &&
- echo TEST+FIXED >F &&
echo Merged initial commit and a later commit. >M &&
echo $h_TEST >.git/MERGE_HEAD &&
GIT_AUTHOR_DATE="2005-05-26 23:45" \
The reason is the test is looking for a very specific date and
time in the branch's reflog. The reflog entry is not impacted by
editing F. Since the commit date is different here the --amend
created a different commit object, which was all that mattered.
Actually I think we probably should just apply the above patch
to clean up this test case. I'll submit it under a different
cover so Junio can more easily apply it.
--
Shawn.
^ permalink raw reply related
* Re: [PATCH] allow bulk import to be packed
From: Junio C Hamano @ 2007-09-06 1:36 UTC (permalink / raw)
To: git; +Cc: Shawn O Pearce
In-Reply-To: <7vps0wfv8g.fsf@gitster.siamese.dyndns.org>
Junio C Hamano <gitster@pobox.com> writes:
> This will use fast-import as a backend to dump blobs resulting
> from a huge initial import into a packfile.
This needs major work to be usable.
Although fast-import knows object name of each blob, being a
quick-and-dirty patch, git-add does not ask for it and still
computes the blob names by itself. The only thing this
proof-of-concept patch buys is that the resulting object
database is packed, and does not get loose objects.
^ permalink raw reply
* Re: Git's database structure
From: Kyle Moffett @ 2007-09-06 1:27 UTC (permalink / raw)
To: Julian Phillips
Cc: Jon Smirl, Andreas Ericsson, Theodore Tso, Junio C Hamano,
Git Mailing List
In-Reply-To: <Pine.LNX.4.64.0709051823470.26016@reaper.quantumfyre.co.uk>
On Sep 05, 2007, at 13:31:43, Julian Phillips wrote:
> And this is advantaged by having the path in the blob how? The
> important information here is knowing which commits touched the
> file - this information is expensive in git because it is snapshot
> based. You have to go back through all the commits looking for
> changes to the given path. The information you might want to cache
> is which commits touched the file, which you could do without
> changing the current data storage. Presumably you are suggesting
> that such a cache would be cleaner with the filename in the blob?
> Or do you think that it would somehow be faster to create? If so,
> how?
The only possible reason I can think of for moving data into the blob
would be to make a POSIX-compliant git-like filesystem, and EVEN THEN
you would NOT move the path out of the tree objects. In order to
have somewhat consistent inodes (and also for performance when
changing 4 bytes in a 40GB file) you would want to have 3 different
types of "inode" objects:
1) 4-64k of (metadata + filedata)
2) 4-64k of (metadata + list of 4-64k filedata blobs)
3) 4-64k of (metadata + list of 4-64k lists of filedata blobs)
On the other hand... that isn't GIT, it's something completely
different with a very different usage pattern and set of
requirements. And you still don't put the path name in the objects,
just the permissions and other attributes/metadata.
<Random Thought Experiment>
You would of course want to better define those 4-64k limits for
allocation and performance reasons, but a double-indirect table of
SHA128s with 64kb chunks lets you address up to 1TB of file data, and
for each additional power-of-two increase in the chunk size you get 8
times the storage space. Furthermore, the actual double-indirect
tables for an 8TB file using 128k chunks would be all of 64MB, for a
more reasonable 4GB file with 32k tables (max of 128GB) it would be
maybe 128kB of indirect SHA1 hash tables.
</Random Thought Experiment>
Cheers,
Kyle Moffett
^ permalink raw reply
* [PATCH] allow bulk import to be packed
From: Junio C Hamano @ 2007-09-06 1:17 UTC (permalink / raw)
To: Shawn O Pearce; +Cc: git
This will use fast-import as a backend to dump blobs resulting
from a huge initial import into a packfile.
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
builtin-add.c | 79 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 79 insertions(+), 0 deletions(-)
diff --git a/builtin-add.c b/builtin-add.c
index 105a9f0..f512108 100644
--- a/builtin-add.c
+++ b/builtin-add.c
@@ -12,6 +12,7 @@
#include "diffcore.h"
#include "commit.h"
#include "revision.h"
+#include "run-command.h"
static const char builtin_add_usage[] =
"git-add [-n] [-v] [-f] [--interactive | -i] [-u] [--refresh] [--] <filepattern>...";
@@ -153,6 +154,82 @@ static int git_add_config(const char *var, const char *value)
return git_default_config(var, value);
}
+static void pack_one_blob(FILE *g, size_t size, const char *path)
+{
+ void *buf = NULL;
+ unsigned long nsize;
+ char *nbuf;
+ int re_allocated = 0;
+
+ if (size) {
+ int fd = open(path, O_RDONLY);
+ if (fd < 0)
+ return;
+ buf = xmmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
+ close(fd);
+ }
+ nbuf = convert_to_git(path, buf, &nsize);
+ if (nbuf) {
+ munmap(buf, size);
+ size = nsize;
+ buf = nbuf;
+ re_allocated = 1;
+ }
+
+ fprintf(g, "blob\ndata %lu\n", size);
+ fwrite(buf, 1, size, g);
+ fputc('\n', g);
+}
+
+#ifndef BULK_ADD_LIMIT
+#define BULK_ADD_LIMIT 200
+#endif
+
+static void pack_bluk_blob(struct dir_struct *dir, int verbose)
+{
+ struct child_process gfi;
+ const char *gfi_argv[] = { "fast-import", NULL };
+ int i, cnt;
+ FILE *g;
+ size_t *addmap;
+
+ if (dir->nr < BULK_ADD_LIMIT)
+ return;
+
+ addmap = xcalloc(dir->nr, sizeof(size_t));
+ for (i = cnt = 0; i < dir->nr; i++) {
+ struct stat st;
+ const char *name = dir->entries[i]->name;
+ if (!lstat(name, &st) && S_ISREG(st.st_mode)) {
+ cnt++;
+ addmap[i] = xsize_t(st.st_size);
+ }
+ }
+ if (cnt < BULK_ADD_LIMIT)
+ return;
+
+ memset(&gfi, 0, sizeof(gfi));
+ gfi.argv = gfi_argv;
+ gfi.no_stdout = 1;
+ gfi.in = -1;
+ gfi.git_cmd = 1;
+
+ if (start_command(&gfi)) {
+ warning("could not run fast-import");
+ return;
+ }
+ g = fdopen(gfi.in, "w");
+
+ for (i = 0; i < dir->nr; i++) {
+ if (!addmap[i])
+ continue;
+ pack_one_blob(g, addmap[i], dir->entries[i]->name);
+ }
+ fflush(g);
+ finish_command(&gfi);
+ reprepare_packed_git();
+}
+
static struct lock_file lock_file;
static const char ignore_error[] =
@@ -258,6 +335,8 @@ int cmd_add(int argc, const char **argv, const char *prefix)
die("no files added");
}
+ pack_bluk_blob(&dir, verbose);
+
for (i = 0; i < dir.nr; i++)
add_file_to_cache(dir.entries[i]->name, verbose);
^ permalink raw reply related
* Re: Significant performance waste in git-svn and friends
From: Patrick Doyle @ 2007-09-06 1:07 UTC (permalink / raw)
To: git
In-Reply-To: <85bqcghktl.fsf@lola.goethe.zz>
I'll add my hearty vote of support, and perhaps even a patch or two,
to anything that would make git-svn faster. Just last week, I gave up
waiting for it to complete the import of a (rather large) uClinux
distribution, falling back on SVN. I'm looking forward to trying it
again with Mike's patch, or perhaps even implementing and trying out
David's idea.
Hmmm.... I'm supposed to be on a couple of planes for a rather longish
period of time next week. Perhaps I'll give it a shot. (But don't
count on much -- the laptop battery is pretty shot nowadays and, given
the nature of the rest of the participitants on this list, it'll
probably be implemented and tested by the time I pack up for the
trip.)
--wpd
^ permalink raw reply
* builtin commit series sent
From: Kristian Høgsberg @ 2007-09-06 0:45 UTC (permalink / raw)
To: Git Mailing List
Hi,
I finally sat down and finished the last details in my git-commit.sh
port and made it pass the test suite. The test suite is cruel, but
awesome. There's one test that doesn't look right to me:
t1400-update-ref.sh. The 'creating initial files' case does a git
commit --amend and apparently expect to commit the changed contents of
the file F, even though it hasn't been added to the index. Similarly,
there's a git commit -F M just below it that does the same thing. I'm
not sure how they pass with the shell script commit...
Also, there is some overlap in the patch set with Pierre's nice strbuf
work, but it's mostly just naming, and should be easy to work out. I
have a couple of places where I manually add a NUL terminator, but I
like Pierres approach of always implicitly doing that.
I didn't work on this for a couple of weeks, and keeping up-to-date with
the various git-commit.sh changes that happened in the meantime
(templates, different option semantics etc) wasn't fun. Also I don't
really have the time to maintain this out of tree, these days, so I'd
really appreciate, now that the work is finished, if we can get it in.
cheers,
Kristian
^ permalink raw reply
* Re: [PATCH 3/3] Makefile: Add cache-tree.h to the public headers list
From: Dmitry V. Levin @ 2007-09-06 0:37 UTC (permalink / raw)
To: Junio C Hamano; +Cc: Git Mailing List
In-Reply-To: <7vy7fkfy1c.fsf@gitster.siamese.dyndns.org>
[-- Attachment #1: Type: text/plain, Size: 364 bytes --]
On Wed, Sep 05, 2007 at 05:16:47PM -0700, Junio C Hamano wrote:
[...]
> I am however not convinced it is a good idea to treat libgit.a
> as if it is a library.
> It is not a library in the usual sense of the word.
I'd certainly prefer libgit.so with stable interface instead, but
parsecvs needs -lgit now, it cannot wait for "good library".
--
ldv
[-- Attachment #2: Type: application/pgp-signature, Size: 189 bytes --]
^ permalink raw reply
* [PATCH 9/9] Implement git commit as a builtin command.
From: Kristian Høgsberg @ 2007-09-06 0:23 UTC (permalink / raw)
To: git; +Cc: Kristian Høgsberg
In-Reply-To: <11890382264046-git-send-email-krh@redhat.com>
Move git-commit.sh to contrib/examples.
Signed-off-by: Kristian Høgsberg <krh@redhat.com>
---
Makefile | 9 +-
builtin-commit.c | 742 ++++++++++++++++++++++++++++++++++++++++
builtin.h | 3 +-
contrib/examples/git-commit.sh | 665 +++++++++++++++++++++++++++++++++++
git-commit.sh | 665 -----------------------------------
git.c | 3 +-
6 files changed, 1414 insertions(+), 673 deletions(-)
create mode 100644 builtin-commit.c
create mode 100755 contrib/examples/git-commit.sh
delete mode 100755 git-commit.sh
diff --git a/Makefile b/Makefile
index 4eb4637..beb3cbb 100644
--- a/Makefile
+++ b/Makefile
@@ -201,7 +201,7 @@ BASIC_LDFLAGS =
SCRIPT_SH = \
git-bisect.sh git-checkout.sh \
- git-clean.sh git-clone.sh git-commit.sh \
+ git-clean.sh git-clone.sh \
git-fetch.sh \
git-ls-remote.sh \
git-merge-one-file.sh git-mergetool.sh git-parse-remote.sh \
@@ -250,7 +250,7 @@ EXTRA_PROGRAMS =
BUILT_INS = \
git-format-patch$X git-show$X git-whatchanged$X git-cherry$X \
git-get-tar-commit-id$X git-init$X git-repo-config$X \
- git-fsck-objects$X git-cherry-pick$X \
+ git-fsck-objects$X git-cherry-pick$X git-status$X\
$(patsubst builtin-%.o,git-%$X,$(BUILTIN_OBJS))
# what 'all' will build and 'install' will install, in gitexecdir
@@ -322,6 +322,7 @@ BUILTIN_OBJS = \
builtin-check-attr.o \
builtin-checkout-index.o \
builtin-check-ref-format.o \
+ builtin-commit.o \
builtin-commit-tree.o \
builtin-count-objects.o \
builtin-describe.o \
@@ -357,7 +358,6 @@ BUILTIN_OBJS = \
builtin-rev-parse.o \
builtin-revert.o \
builtin-rm.o \
- builtin-runstatus.o \
builtin-shortlog.o \
builtin-show-branch.o \
builtin-stripspace.o \
@@ -797,9 +797,6 @@ $(patsubst %.perl,%,$(SCRIPT_PERL)): % : %.perl
chmod +x $@+ && \
mv $@+ $@
-git-status: git-commit
- $(QUIET_GEN)cp $< $@+ && mv $@+ $@
-
gitweb/gitweb.cgi: gitweb/gitweb.perl
$(QUIET_GEN)$(RM) $@ $@+ && \
sed -e '1s|#!.*perl|#!$(PERL_PATH_SQ)|' \
diff --git a/builtin-commit.c b/builtin-commit.c
new file mode 100644
index 0000000..aca5ff8
--- /dev/null
+++ b/builtin-commit.c
@@ -0,0 +1,742 @@
+/*
+ * Builtin "git commit"
+ *
+ * Copyright (c) 2007 Kristian Høgsberg <krh@redhat.com>
+ * Based on git-commit.sh by Junio C Hamano and Linus Torvalds
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "cache.h"
+#include "cache-tree.h"
+#include "builtin.h"
+#include "diff.h"
+#include "diffcore.h"
+#include "commit.h"
+#include "revision.h"
+#include "wt-status.h"
+#include "run-command.h"
+#include "refs.h"
+#include "log-tree.h"
+#include "strbuf.h"
+#include "utf8.h"
+
+void launch_editor(const char *path, struct strbuf *sb);
+int rerere(void);
+
+static const char builtin_commit_usage[] =
+ "[-a | --interactive] [-s] [-v] [--no-verify] [-m <message> | -F <logfile> | (-C|-c) <commit> | --amend] [-u] [-e] [--author <author>] [--template <file>] [[-i | -o] <path>...]";
+
+static unsigned char head_sha1[20], merge_head_sha1[20];
+static char *use_message_buffer;
+static const char commit_editmsg[] = "COMMIT_EDITMSG";
+static struct lock_file lock_file;
+
+enum option_type {
+ OPTION_NONE,
+ OPTION_STRING,
+ OPTION_INTEGER,
+ OPTION_LAST,
+};
+
+struct option {
+ enum option_type type;
+ const char *long_name;
+ char short_name;
+ void *value;
+};
+
+static int scan_options(const char ***argv, struct option *options)
+{
+ const char *value, *eq;
+ int i;
+
+ if (**argv == NULL)
+ return 0;
+ if ((**argv)[0] != '-')
+ return 0;
+ if (!strcmp(**argv, "--"))
+ return 0;
+
+ value = NULL;
+ for (i = 0; options[i].type != OPTION_LAST; i++) {
+ if ((**argv)[1] == '-') {
+ if (!prefixcmp(options[i].long_name, **argv + 2)) {
+ if (options[i].type != OPTION_NONE)
+ value = *++(*argv);
+ goto match;
+ }
+
+ eq = strchr(**argv + 2, '=');
+ if (eq && options[i].type != OPTION_NONE &&
+ !strncmp(**argv + 2,
+ options[i].long_name, eq - **argv - 2)) {
+ value = eq + 1;
+ goto match;
+ }
+ }
+
+ if ((**argv)[1] == options[i].short_name) {
+ if ((**argv)[2] == '\0') {
+ if (options[i].type != OPTION_NONE)
+ value = *++(*argv);
+ goto match;
+ }
+
+ if (options[i].type != OPTION_NONE) {
+ value = **argv + 2;
+ goto match;
+ }
+ }
+ }
+
+ usage(builtin_commit_usage);
+
+ match:
+ switch (options[i].type) {
+ case OPTION_NONE:
+ *(int *)options[i].value = 1;
+ break;
+ case OPTION_STRING:
+ if (value == NULL)
+ die("option %s requires a value.", (*argv)[-1]);
+ *(const char **)options[i].value = value;
+ break;
+ case OPTION_INTEGER:
+ if (value == NULL)
+ die("option %s requires a value.", (*argv)[-1]);
+ *(int *)options[i].value = atoi(value);
+ break;
+ default:
+ assert(0);
+ }
+
+ (*argv)++;
+
+ return 1;
+}
+
+static char *logfile, *force_author, *message, *template_file;
+static char *edit_message, *use_message;
+static int all, edit_flag, also, interactive, only, no_verify, amend, signoff;
+static int quiet, verbose, untracked_files;
+
+static int no_edit, initial_commit, in_merge;
+const char *only_include_assumed;
+
+static struct option commit_options[] = {
+ { OPTION_STRING, "file", 'F', (void *) &logfile },
+ { OPTION_NONE, "all", 'a', &all },
+ { OPTION_STRING, "author", 0, (void *) &force_author },
+ { OPTION_NONE, "edit", 'e', &edit_flag },
+ { OPTION_NONE, "include", 'i', &also },
+ { OPTION_NONE, "interactive", 0, &interactive },
+ { OPTION_NONE, "only", 'o', &only },
+ { OPTION_STRING, "message", 'm', &message },
+ { OPTION_NONE, "no-verify", 'n', &no_verify },
+ { OPTION_NONE, "amend", 0, &amend },
+ { OPTION_STRING, "reedit-message", 'c', &edit_message },
+ { OPTION_STRING, "reuse-message", 'C', &use_message },
+ { OPTION_NONE, "signoff", 's', &signoff },
+ { OPTION_NONE, "quiet", 'q', &quiet },
+ { OPTION_NONE, "verbose", 'v', &verbose },
+ { OPTION_NONE, "untracked-files", 0, &untracked_files },
+ { OPTION_STRING, "template", 't', &template_file },
+ { OPTION_LAST },
+};
+
+/* FIXME: Taken from builtin-add, should be shared. */
+
+static void update_callback(struct diff_queue_struct *q,
+ struct diff_options *opt, void *cbdata)
+{
+ int i, verbose;
+
+ verbose = *((int *)cbdata);
+ for (i = 0; i < q->nr; i++) {
+ struct diff_filepair *p = q->queue[i];
+ const char *path = p->one->path;
+ switch (p->status) {
+ default:
+ die("unexpacted diff status %c", p->status);
+ case DIFF_STATUS_UNMERGED:
+ case DIFF_STATUS_MODIFIED:
+ case DIFF_STATUS_TYPE_CHANGED:
+ add_file_to_cache(path, verbose);
+ break;
+ case DIFF_STATUS_DELETED:
+ remove_file_from_cache(path);
+ cache_tree_invalidate_path(active_cache_tree, path);
+ if (verbose)
+ printf("remove '%s'\n", path);
+ break;
+ }
+ }
+}
+
+static void
+add_files_to_cache(int fd, const char **files, const char *prefix)
+{
+ struct rev_info rev;
+
+ init_revisions(&rev, "");
+ setup_revisions(0, NULL, &rev, NULL);
+ rev.prune_data = get_pathspec(prefix, files);
+ rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK;
+ rev.diffopt.format_callback = update_callback;
+ rev.diffopt.format_callback_data = &verbose;
+
+ run_diff_files(&rev, 0);
+ refresh_cache(REFRESH_QUIET);
+
+ if (write_cache(fd, active_cache, active_nr) || close(fd))
+ die("unable to write new index file");
+}
+
+static char *
+prepare_index(const char **files, const char *prefix)
+{
+ int fd;
+ struct tree *tree;
+ struct lock_file *next_index_lock;
+
+ fd = hold_locked_index(&lock_file, 1);
+ if (read_cache() < 0)
+ die("index file corrupt");
+
+ if (all) {
+ add_files_to_cache(fd, files, NULL);
+ return lock_file.filename;
+ } else if (also) {
+ add_files_to_cache(fd, files, prefix);
+ return lock_file.filename;
+ }
+
+ if (interactive)
+ interactive_add();
+
+ if (*files == NULL) {
+ /* Commit index as-is. */
+ rollback_lock_file(&lock_file);
+ return get_index_file();
+ }
+
+ /*
+ * FIXME: Warn on unknown files. Shell script does
+ *
+ * commit_only=`git-ls-files --error-unmatch -- "$@"`
+ */
+
+ /*
+ * FIXME: shell script does
+ *
+ * git-read-tree --index-output="$TMP_INDEX" -i -m HEAD
+ *
+ * which warns about unmerged files in the index.
+ */
+
+ /* update the user index file */
+ add_files_to_cache(fd, files, prefix);
+
+ if (!initial_commit) {
+ tree = parse_tree_indirect(head_sha1);
+ if (!tree)
+ die("failed to unpack HEAD tree object");
+ if (read_tree(tree, 0, NULL))
+ die("failed to read HEAD tree object");
+ }
+
+ /* Uh oh, abusing lock_file to create a garbage collected file */
+ next_index_lock = xmalloc(sizeof(*next_index_lock));
+ fd = hold_lock_file_for_update(next_index_lock,
+ git_path("next-index-%d", getpid()), 1);
+ add_files_to_cache(fd, files, prefix);
+
+ return next_index_lock->filename;
+}
+
+static int run_status(FILE *fp, const char *index_file)
+{
+ struct wt_status s;
+
+ wt_status_prepare(&s);
+
+ if (amend) {
+ s.amend = 1;
+ s.reference = "HEAD^1";
+ }
+ s.verbose = verbose;
+ s.untracked = untracked_files;
+ s.index_file = index_file;
+ s.fp = fp;
+
+ wt_status_print(&s);
+
+ return s.commitable;
+}
+
+static const char sign_off_header[] = "Signed-off-by: ";
+
+static int prepare_log_message(const char *index_file)
+{
+ struct stat statbuf;
+ int commitable;
+ struct strbuf sb;
+ char *buffer;
+ FILE *fp;
+
+ strbuf_init(&sb);
+ if (message) {
+ strbuf_add(&sb, message, strlen(message));
+ } else if (logfile && !strcmp(logfile, "-")) {
+ if (isatty(0))
+ fprintf(stderr, "(reading log message from standard input)\n");
+ if (strbuf_read_fd(&sb, 0) < 0)
+ die("could not read log from standard input");
+ } else if (logfile) {
+ if (strbuf_read_path(&sb, logfile) < 0)
+ die("could not read log file '%s': %s",
+ logfile, strerror(errno));
+ } else if (use_message) {
+ buffer = strstr(use_message_buffer, "\n\n");
+ if (!buffer || buffer[2] == '\0')
+ die("commit has empty message");
+ strbuf_add(&sb, buffer + 2, strlen(buffer + 2));
+ } else if (!stat(git_path("MERGE_MSG"), &statbuf)) {
+ if (strbuf_read_path(&sb, git_path("MERGE_MSG")) < 0)
+ die("could not read MERGE_MSG: %s", strerror(errno));
+ } else if (!stat(git_path("SQUASH_MSG"), &statbuf)) {
+ if (strbuf_read_path(&sb, git_path("SQUASH_MSG")) < 0)
+ die("could not read SQUASH_MSG: %s", strerror(errno));
+ } else if (!stat(template_file, &statbuf)) {
+ if (strbuf_read_path(&sb, template_file) < 0)
+ die("could not read %s: %s",
+ template_file, strerror(errno));
+ }
+
+ fp = fopen(git_path(commit_editmsg), "w");
+ if (fp == NULL)
+ die("could not open %s\n", git_path(commit_editmsg));
+
+ stripspace(&sb, 0);
+ if (fwrite(sb.buf, 1, sb.len, fp) < sb.len)
+ die("could not write commit template: %s\n",
+ strerror(errno));
+
+ if (signoff) {
+ const char *info, *bol;
+
+ info = git_committer_info(1);
+ strbuf_add_char(&sb, '\0');
+ bol = strrchr(sb.buf + sb.len - 1, '\n');
+ if (!bol || prefixcmp(bol, sign_off_header))
+ fprintf(fp, "\n");
+ fprintf(fp, "Signed-off-by: %s\n", git_committer_info(1));
+ }
+
+ strbuf_release(&sb);
+
+ if (in_merge && !no_edit) {
+ fprintf(fp,
+ "#\n"
+ "# It looks like you may be committing a MERGE.\n"
+ "# If this is not correct, please remove the file\n"
+ "# %s\n"
+ "# and try again.\n"
+ "#\n",
+ git_path("MERGE_HEAD"));
+ }
+
+ fprintf(fp,
+ "\n"
+ "# Please enter the commit message for your changes.\n"
+ "# (Comment lines starting with '#' will not be included)\n");
+ if (only_include_assumed)
+ fprintf(fp, "# %s\n", only_include_assumed);
+
+ commitable = run_status(fp, index_file);
+
+ fclose(fp);
+
+ return commitable;
+}
+
+/* Find out if the message starting at position 'start' in the strbuf
+ * contains only whitespace and Signed-off-by lines. */
+static int message_is_empty(struct strbuf *sb, int start)
+{
+ static const char signed_off_by[] = "Signed-off-by: ";
+ struct strbuf tmpl;
+ const char *nl;
+ int eol, i;
+
+ /* See if the template is just a prefix of the message. */
+ strbuf_init(&tmpl);
+ if (template_file && strbuf_read_path(&tmpl, template_file) > 0) {
+ stripspace(&tmpl, 1);
+ if (start + tmpl.len <= sb->len &&
+ memcmp(tmpl.buf, sb->buf + start, tmpl.len) == 0)
+ start += tmpl.len;
+ }
+ strbuf_release(&tmpl);
+
+ /* Check if the rest is just whitespace and Signed-of-by's. */
+ for (i = start; i < sb->len; i++) {
+ nl = memchr(sb->buf + i, '\n', sb->len - i);
+ if (nl)
+ eol = nl - sb->buf;
+ else
+ eol = sb->len;
+
+ if (strlen(signed_off_by) <= eol - i &&
+ !prefixcmp(sb->buf + i, signed_off_by)) {
+ i = eol;
+ continue;
+ }
+ while (i < eol)
+ if (!isspace(sb->buf[i++]))
+ return 0;
+ }
+
+ return 1;
+}
+
+static void determine_author_info(struct strbuf *sb)
+{
+ char *p, *eol;
+ char *name = NULL, *email = NULL;
+
+ if (use_message) {
+ p = strstr(use_message_buffer, "\nauthor");
+ if (!p)
+ die("invalid commit: %s\n", use_message);
+ p++;
+ eol = strchr(p, '\n');
+ if (!eol)
+ die("invalid commit: %s\n", use_message);
+
+ strbuf_add(sb, p, eol + 1 - p);
+ } else if (force_author) {
+ const char *eoname = strstr(force_author, " <");
+ const char *eomail = strchr(force_author, '>');
+
+ if (!eoname || !eomail)
+ die("malformed --author parameter\n");
+ name = xstrndup(force_author, eoname - force_author);
+ email = xstrndup(eoname + 2, eomail - eoname - 2);
+ strbuf_printf(sb, "author %s\n",
+ fmt_ident(name, email,
+ getenv("GIT_AUTHOR_DATE"), 1));
+ free(name);
+ free(email);
+ } else {
+ strbuf_printf(sb, "author %s\n", git_author_info(1));
+ }
+}
+
+static void parse_and_validate_options(const char ***argv)
+{
+ int f = 0;
+
+ (*argv)++;
+ while (scan_options(argv, commit_options))
+ ;
+
+ if (logfile || message || use_message)
+ no_edit = 1;
+ if (edit_flag)
+ no_edit = 0;
+
+ if (get_sha1("HEAD", head_sha1))
+ initial_commit = 1;
+
+ if (!get_sha1("MERGE_HEAD", merge_head_sha1))
+ in_merge = 1;
+
+ /* Sanity check options */
+ if (amend && initial_commit)
+ die("You have nothing to amend.");
+ if (amend && in_merge)
+ die("You are in the middle of a merger -- cannot amend.");
+
+ if (use_message)
+ f++;
+ if (edit_message)
+ f++;
+ if (logfile)
+ f++;
+ if (f > 1)
+ die("Only one of -c/-C/-F can be used.");
+ if (message && f > 0)
+ die("Option -m cannot be combined with -c/-C/-F.");
+ if (edit_message)
+ use_message = edit_message;
+ if (amend)
+ use_message = "HEAD";
+ if (use_message) {
+ unsigned char sha1[20];
+ static char utf8[] = "UTF-8";
+ char *enc, *end, *out_enc;
+ struct commit *commit;
+
+ if (get_sha1(use_message, sha1))
+ die("could not lookup commit %s", use_message);
+ commit = lookup_commit(sha1);
+ if (!commit || parse_commit(commit))
+ die("could not parse commit %s", use_message);
+
+ enc = strstr(commit->buffer, "\nencoding");
+ if (enc) {
+ end = strchr(enc + 10, '\n');
+ enc = xstrndup(enc + 10, end - (enc + 10));
+ } else {
+ enc = utf8;
+ }
+ out_enc = git_commit_encoding ? git_commit_encoding : utf8;
+
+ use_message_buffer =
+ reencode_string(commit->buffer, out_enc, enc);
+ if (enc != utf8)
+ free(enc);
+ }
+
+ if (also && only)
+ die("Only one of --include/--only can be used.");
+ if (!*argv && (also || (only && !amend)))
+ die("No paths with --include/--only does not make sense.");
+ if (!*argv && only && amend)
+ only_include_assumed = "Clever... amending the last one with dirty index.";
+ if (*argv && !also && !only) {
+ only_include_assumed = "Explicit paths specified without -i nor -o; assuming --only paths...";
+ also = 0;
+ }
+
+ if (all && interactive)
+ die("Cannot use -a, --interactive or -i at the same time.");
+ else if (all && **argv)
+ die("Paths with -a does not make sense.");
+ else if (interactive && **argv)
+ die("Paths with --interactive does not make sense.");
+}
+
+int cmd_status(int argc, const char **argv, const char *prefix)
+{
+ const char *index_file;
+ int commitable;
+
+ git_config(git_status_config);
+
+ parse_and_validate_options(&argv);
+
+ index_file = prepare_index(argv, prefix);
+
+ commitable = run_status(stdout, index_file);
+
+ rollback_lock_file(&lock_file);
+
+ return commitable ? 0 : 1;
+}
+
+static int run_hook(const char *index_file, const char *name, const char *arg)
+{
+ struct child_process hook;
+ const char *argv[3], *env[2];
+ char index[PATH_MAX];
+
+ argv[0] = git_path("hooks/%s", name);
+ argv[1] = arg;
+ argv[2] = NULL;
+ snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", index_file);
+ env[0] = index;
+ env[1] = NULL;
+
+ if (access(argv[0], X_OK) < 0)
+ return 0;
+
+ memset(&hook, 0, sizeof(hook));
+ hook.argv = argv;
+ hook.no_stdin = 1;
+ hook.stdout_to_stderr = 1;
+ hook.env = env;
+
+ return run_command(&hook);
+}
+
+static void print_summary(const char *prefix, const unsigned char *sha1)
+{
+ struct rev_info rev;
+ struct commit *commit;
+
+ commit = lookup_commit(sha1);
+ if (!commit)
+ die("couldn't look up newly created commit\n");
+ if (!commit || parse_commit(commit))
+ die("could not parse newly created commit");
+
+ init_revisions(&rev, prefix);
+ setup_revisions(0, NULL, &rev, NULL);
+
+ rev.abbrev = 0;
+ rev.diff = 1;
+ rev.diffopt.output_format =
+ DIFF_FORMAT_SHORTSTAT | DIFF_FORMAT_SUMMARY;
+
+ rev.verbose_header = 1;
+ rev.show_root_diff = 1;
+ rev.commit_format = get_commit_format("format:%h: %s");
+ rev.always_show_header = 1;
+
+ printf("Created %scommit ", initial_commit ? "initial " : "");
+
+ log_tree_commit(&rev, commit);
+}
+
+int git_commit_config(const char *k, const char *v)
+{
+ if (!strcmp(k, "commit.template")) {
+ template_file = xstrdup(v);
+ return 0;
+ }
+
+ return git_status_config(k, v);
+}
+
+static const char commit_utf8_warn[] =
+"Warning: commit message does not conform to UTF-8.\n"
+"You may want to amend it after fixing the message, or set the config\n"
+"variable i18n.commitencoding to the encoding your project uses.\n";
+
+int cmd_commit(int argc, const char **argv, const char *prefix)
+{
+ int header_len, parent_count = 0;
+ struct strbuf sb;
+ const char *index_file, *reflog_msg;
+ char *nl, *header_line;
+ unsigned char commit_sha1[20];
+ struct ref_lock *ref_lock;
+
+ git_config(git_commit_config);
+
+ parse_and_validate_options(&argv);
+
+ index_file = prepare_index(argv, prefix);
+
+ if (run_hook(index_file, "pre-commit", NULL))
+ exit(1);
+
+ if (!prepare_log_message(index_file)) {
+ run_status(stdout, index_file);
+ unlink(commit_editmsg);
+ return 1;
+ }
+
+ strbuf_init(&sb);
+
+ /* Start building up the commit header */
+ read_cache_from(index_file);
+ active_cache_tree = cache_tree();
+ if (cache_tree_update(active_cache_tree,
+ active_cache, active_nr, 0, 0) < 0)
+ die("Error building trees");
+ strbuf_printf(&sb, "tree %s\n",
+ sha1_to_hex(active_cache_tree->sha1));
+
+ /* Determine parents */
+ if (initial_commit) {
+ reflog_msg = "commit (initial)";
+ parent_count = 0;
+ } else if (amend) {
+ struct commit_list *c;
+ struct commit *commit;
+
+ reflog_msg = "commit (amend)";
+ commit = lookup_commit(head_sha1);
+ if (!commit || parse_commit(commit))
+ die("could not parse HEAD commit");
+
+ for (c = commit->parents; c; c = c->next)
+ strbuf_printf(&sb, "parent %s\n",
+ sha1_to_hex(c->item->object.sha1));
+ } else if (in_merge) {
+ struct strbuf m;
+ FILE *fp;
+
+ reflog_msg = "commit (merge)";
+ strbuf_printf(&sb, "parent %s\n", sha1_to_hex(head_sha1));
+ strbuf_init(&m);
+ fp = fopen(git_path("MERGE_HEAD"), "r");
+ if (fp == NULL)
+ die("could not open %s for reading: %s",
+ git_path("MERGE_HEAD"), strerror(errno));
+ while (!m.eof) {
+ read_line(&m, fp, '\n');
+ if (!m.eof)
+ strbuf_printf(&sb, "parent %s\n", m.buf);
+ }
+ fclose(fp);
+ strbuf_release(&m);
+ } else {
+ reflog_msg = "commit";
+ strbuf_printf(&sb, "parent %s\n", sha1_to_hex(head_sha1));
+ }
+
+ determine_author_info(&sb);
+ strbuf_printf(&sb, "committer %s\n", git_committer_info(1));
+ if (!is_encoding_utf8(git_commit_encoding))
+ strbuf_printf(&sb, "encoding %s\n", git_commit_encoding);
+ strbuf_add_char(&sb, '\n');
+
+ /* Get the commit message and validate it */
+ header_len = sb.len;
+ if (!no_edit) {
+ fprintf(stderr, "launching editor, log %s\n", logfile);
+ launch_editor(git_path(commit_editmsg), &sb);
+ }
+ else if (strbuf_read_path(&sb, git_path(commit_editmsg)) < 0)
+ die("could not read commit message\n");
+ if (run_hook(index_file, "commit-msg", commit_editmsg))
+ exit(1);
+ if (stripspace(&sb, 1) < header_len ||
+ message_is_empty(&sb, header_len))
+ die("* no commit message? aborting commit.");
+ strbuf_add_char(&sb, '\0');
+ if (is_encoding_utf8(git_commit_encoding) && !is_utf8(sb.buf))
+ fprintf(stderr, commit_utf8_warn);
+
+ if (write_sha1_file(sb.buf, sb.len - 1, commit_type, commit_sha1))
+ die("failed to write commit object");
+
+ ref_lock = lock_any_ref_for_update("HEAD",
+ initial_commit ? NULL : head_sha1,
+ 0);
+
+ nl = strchr(sb.buf + header_len, '\n');
+ header_line = xstrndup(sb.buf + header_len,
+ nl - (sb.buf + header_len));
+ strbuf_release(&sb);
+ strbuf_printf(&sb, "%s: %s\n", reflog_msg, header_line);
+ strbuf_add_char(&sb, '\0');
+ free(header_line);
+
+ if (!ref_lock)
+ die("cannot lock HEAD ref");
+ if (write_ref_sha1(ref_lock, commit_sha1, sb.buf) < 0)
+ die("cannot update HEAD ref");
+
+ unlink(git_path("MERGE_HEAD"));
+ unlink(git_path("MERGE_MSG"));
+
+ if (lock_file.filename[0] && commit_locked_index(&lock_file))
+ die("failed to write new index");
+
+ rerere();
+
+ run_hook(index_file, "post-commit", NULL);
+
+ if (!quiet)
+ print_summary(prefix, commit_sha1);
+
+ return 0;
+}
diff --git a/builtin.h b/builtin.h
index 91bc595..c23cd6d 100644
--- a/builtin.h
+++ b/builtin.h
@@ -23,6 +23,7 @@ extern int cmd_check_attr(int argc, const char **argv, const char *prefix);
extern int cmd_check_ref_format(int argc, const char **argv, const char *prefix);
extern int cmd_cherry(int argc, const char **argv, const char *prefix);
extern int cmd_cherry_pick(int argc, const char **argv, const char *prefix);
+extern int cmd_commit(int argc, const char **argv, const char *prefix);
extern int cmd_commit_tree(int argc, const char **argv, const char *prefix);
extern int cmd_count_objects(int argc, const char **argv, const char *prefix);
extern int cmd_describe(int argc, const char **argv, const char *prefix);
@@ -63,10 +64,10 @@ extern int cmd_rev_list(int argc, const char **argv, const char *prefix);
extern int cmd_rev_parse(int argc, const char **argv, const char *prefix);
extern int cmd_revert(int argc, const char **argv, const char *prefix);
extern int cmd_rm(int argc, const char **argv, const char *prefix);
-extern int cmd_runstatus(int argc, const char **argv, const char *prefix);
extern int cmd_shortlog(int argc, const char **argv, const char *prefix);
extern int cmd_show(int argc, const char **argv, const char *prefix);
extern int cmd_show_branch(int argc, const char **argv, const char *prefix);
+extern int cmd_status(int argc, const char **argv, const char *prefix);
extern int cmd_stripspace(int argc, const char **argv, const char *prefix);
extern int cmd_symbolic_ref(int argc, const char **argv, const char *prefix);
extern int cmd_tag(int argc, const char **argv, const char *prefix);
diff --git a/contrib/examples/git-commit.sh b/contrib/examples/git-commit.sh
new file mode 100755
index 0000000..d7e7028
--- /dev/null
+++ b/contrib/examples/git-commit.sh
@@ -0,0 +1,665 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Linus Torvalds
+# Copyright (c) 2006 Junio C Hamano
+
+USAGE='[-a | --interactive] [-s] [-v] [--no-verify] [-m <message> | -F <logfile> | (-C|-c) <commit> | --amend] [-u] [-e] [--author <author>] [--template <file>] [[-i | -o] <path>...]'
+SUBDIRECTORY_OK=Yes
+. git-sh-setup
+require_work_tree
+
+git rev-parse --verify HEAD >/dev/null 2>&1 || initial_commit=t
+
+case "$0" in
+*status)
+ status_only=t
+ ;;
+*commit)
+ status_only=
+ ;;
+esac
+
+refuse_partial () {
+ echo >&2 "$1"
+ echo >&2 "You might have meant to say 'git commit -i paths...', perhaps?"
+ exit 1
+}
+
+THIS_INDEX="$GIT_DIR/index"
+NEXT_INDEX="$GIT_DIR/next-index$$"
+rm -f "$NEXT_INDEX"
+save_index () {
+ cp -p "$THIS_INDEX" "$NEXT_INDEX"
+}
+
+run_status () {
+ # If TMP_INDEX is defined, that means we are doing
+ # "--only" partial commit, and that index file is used
+ # to build the tree for the commit. Otherwise, if
+ # NEXT_INDEX exists, that is the index file used to
+ # make the commit. Otherwise we are using as-is commit
+ # so the regular index file is what we use to compare.
+ if test '' != "$TMP_INDEX"
+ then
+ GIT_INDEX_FILE="$TMP_INDEX"
+ export GIT_INDEX_FILE
+ elif test -f "$NEXT_INDEX"
+ then
+ GIT_INDEX_FILE="$NEXT_INDEX"
+ export GIT_INDEX_FILE
+ fi
+
+ case "$status_only" in
+ t) color= ;;
+ *) color=--nocolor ;;
+ esac
+ git runstatus ${color} \
+ ${verbose:+--verbose} \
+ ${amend:+--amend} \
+ ${untracked_files:+--untracked}
+}
+
+trap '
+ test -z "$TMP_INDEX" || {
+ test -f "$TMP_INDEX" && rm -f "$TMP_INDEX"
+ }
+ rm -f "$NEXT_INDEX"
+' 0
+
+################################################################
+# Command line argument parsing and sanity checking
+
+all=
+also=
+interactive=
+only=
+logfile=
+use_commit=
+amend=
+edit_flag=
+no_edit=
+log_given=
+log_message=
+verify=t
+quiet=
+verbose=
+signoff=
+force_author=
+only_include_assumed=
+untracked_files=
+templatefile="`git config commit.template`"
+while case "$#" in 0) break;; esac
+do
+ case "$1" in
+ -F|--F|-f|--f|--fi|--fil|--file)
+ case "$#" in 1) usage ;; esac
+ shift
+ no_edit=t
+ log_given=t$log_given
+ logfile="$1"
+ shift
+ ;;
+ -F*|-f*)
+ no_edit=t
+ log_given=t$log_given
+ logfile=`expr "z$1" : 'z-[Ff]\(.*\)'`
+ shift
+ ;;
+ --F=*|--f=*|--fi=*|--fil=*|--file=*)
+ no_edit=t
+ log_given=t$log_given
+ logfile=`expr "z$1" : 'z-[^=]*=\(.*\)'`
+ shift
+ ;;
+ -a|--a|--al|--all)
+ all=t
+ shift
+ ;;
+ --au=*|--aut=*|--auth=*|--autho=*|--author=*)
+ force_author=`expr "z$1" : 'z-[^=]*=\(.*\)'`
+ shift
+ ;;
+ --au|--aut|--auth|--autho|--author)
+ case "$#" in 1) usage ;; esac
+ shift
+ force_author="$1"
+ shift
+ ;;
+ -e|--e|--ed|--edi|--edit)
+ edit_flag=t
+ shift
+ ;;
+ -i|--i|--in|--inc|--incl|--inclu|--includ|--include)
+ also=t
+ shift
+ ;;
+ --int|--inte|--inter|--intera|--interac|--interact|--interacti|\
+ --interactiv|--interactive)
+ interactive=t
+ shift
+ ;;
+ -o|--o|--on|--onl|--only)
+ only=t
+ shift
+ ;;
+ -m|--m|--me|--mes|--mess|--messa|--messag|--message)
+ case "$#" in 1) usage ;; esac
+ shift
+ log_given=m$log_given
+ if test "$log_message" = ''
+ then
+ log_message="$1"
+ else
+ log_message="$log_message
+
+$1"
+ fi
+ no_edit=t
+ shift
+ ;;
+ -m*)
+ log_given=m$log_given
+ if test "$log_message" = ''
+ then
+ log_message=`expr "z$1" : 'z-m\(.*\)'`
+ else
+ log_message="$log_message
+
+`expr "z$1" : 'z-m\(.*\)'`"
+ fi
+ no_edit=t
+ shift
+ ;;
+ --m=*|--me=*|--mes=*|--mess=*|--messa=*|--messag=*|--message=*)
+ log_given=m$log_given
+ if test "$log_message" = ''
+ then
+ log_message=`expr "z$1" : 'z-[^=]*=\(.*\)'`
+ else
+ log_message="$log_message
+
+`expr "z$1" : 'zq-[^=]*=\(.*\)'`"
+ fi
+ no_edit=t
+ shift
+ ;;
+ -n|--n|--no|--no-|--no-v|--no-ve|--no-ver|--no-veri|--no-verif|\
+ --no-verify)
+ verify=
+ shift
+ ;;
+ --a|--am|--ame|--amen|--amend)
+ amend=t
+ use_commit=HEAD
+ shift
+ ;;
+ -c)
+ case "$#" in 1) usage ;; esac
+ shift
+ log_given=t$log_given
+ use_commit="$1"
+ no_edit=
+ shift
+ ;;
+ --ree=*|--reed=*|--reedi=*|--reedit=*|--reedit-=*|--reedit-m=*|\
+ --reedit-me=*|--reedit-mes=*|--reedit-mess=*|--reedit-messa=*|\
+ --reedit-messag=*|--reedit-message=*)
+ log_given=t$log_given
+ use_commit=`expr "z$1" : 'z-[^=]*=\(.*\)'`
+ no_edit=
+ shift
+ ;;
+ --ree|--reed|--reedi|--reedit|--reedit-|--reedit-m|--reedit-me|\
+ --reedit-mes|--reedit-mess|--reedit-messa|--reedit-messag|\
+ --reedit-message)
+ case "$#" in 1) usage ;; esac
+ shift
+ log_given=t$log_given
+ use_commit="$1"
+ no_edit=
+ shift
+ ;;
+ -C)
+ case "$#" in 1) usage ;; esac
+ shift
+ log_given=t$log_given
+ use_commit="$1"
+ no_edit=t
+ shift
+ ;;
+ --reu=*|--reus=*|--reuse=*|--reuse-=*|--reuse-m=*|--reuse-me=*|\
+ --reuse-mes=*|--reuse-mess=*|--reuse-messa=*|--reuse-messag=*|\
+ --reuse-message=*)
+ log_given=t$log_given
+ use_commit=`expr "z$1" : 'z-[^=]*=\(.*\)'`
+ no_edit=t
+ shift
+ ;;
+ --reu|--reus|--reuse|--reuse-|--reuse-m|--reuse-me|--reuse-mes|\
+ --reuse-mess|--reuse-messa|--reuse-messag|--reuse-message)
+ case "$#" in 1) usage ;; esac
+ shift
+ log_given=t$log_given
+ use_commit="$1"
+ no_edit=t
+ shift
+ ;;
+ -s|--s|--si|--sig|--sign|--signo|--signof|--signoff)
+ signoff=t
+ shift
+ ;;
+ -t|--t|--te|--tem|--temp|--templ|--templa|--templat|--template)
+ case "$#" in 1) usage ;; esac
+ shift
+ templatefile="$1"
+ no_edit=
+ shift
+ ;;
+ -q|--q|--qu|--qui|--quie|--quiet)
+ quiet=t
+ shift
+ ;;
+ -v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose)
+ verbose=t
+ shift
+ ;;
+ -u|--u|--un|--unt|--untr|--untra|--untrac|--untrack|--untracke|\
+ --untracked|--untracked-|--untracked-f|--untracked-fi|--untracked-fil|\
+ --untracked-file|--untracked-files)
+ untracked_files=t
+ shift
+ ;;
+ --)
+ shift
+ break
+ ;;
+ -*)
+ usage
+ ;;
+ *)
+ break
+ ;;
+ esac
+done
+case "$edit_flag" in t) no_edit= ;; esac
+
+################################################################
+# Sanity check options
+
+case "$amend,$initial_commit" in
+t,t)
+ die "You do not have anything to amend." ;;
+t,)
+ if [ -f "$GIT_DIR/MERGE_HEAD" ]; then
+ die "You are in the middle of a merge -- cannot amend."
+ fi ;;
+esac
+
+case "$log_given" in
+tt*)
+ die "Only one of -c/-C/-F can be used." ;;
+*tm*|*mt*)
+ die "Option -m cannot be combined with -c/-C/-F." ;;
+esac
+
+case "$#,$also,$only,$amend" in
+*,t,t,*)
+ die "Only one of --include/--only can be used." ;;
+0,t,,* | 0,,t,)
+ die "No paths with --include/--only does not make sense." ;;
+0,,t,t)
+ only_include_assumed="# Clever... amending the last one with dirty index." ;;
+0,,,*)
+ ;;
+*,,,*)
+ only_include_assumed="# Explicit paths specified without -i nor -o; assuming --only paths..."
+ also=
+ ;;
+esac
+unset only
+case "$all,$interactive,$also,$#" in
+*t,*t,*)
+ die "Cannot use -a, --interactive or -i at the same time." ;;
+t,,[1-9]*)
+ die "Paths with -a does not make sense." ;;
+,t,[1-9]*)
+ die "Paths with --interactive does not make sense." ;;
+,,t,0)
+ die "No paths with -i does not make sense." ;;
+esac
+
+if test ! -z "$templatefile" -a -z "$log_given"
+then
+ if test ! -f "$templatefile"
+ then
+ die "Commit template file does not exist."
+ fi
+fi
+
+################################################################
+# Prepare index to have a tree to be committed
+
+case "$all,$also" in
+t,)
+ if test ! -f "$THIS_INDEX"
+ then
+ die 'nothing to commit (use "git add file1 file2" to include for commit)'
+ fi
+ save_index &&
+ (
+ cd_to_toplevel &&
+ GIT_INDEX_FILE="$NEXT_INDEX" &&
+ export GIT_INDEX_FILE &&
+ git diff-files --name-only -z |
+ git update-index --remove -z --stdin
+ ) || exit
+ ;;
+,t)
+ save_index &&
+ git ls-files --error-unmatch -- "$@" >/dev/null || exit
+
+ git diff-files --name-only -z -- "$@" |
+ (
+ cd_to_toplevel &&
+ GIT_INDEX_FILE="$NEXT_INDEX" &&
+ export GIT_INDEX_FILE &&
+ git update-index --remove -z --stdin
+ ) || exit
+ ;;
+,)
+ if test "$interactive" = t; then
+ git add --interactive || exit
+ fi
+ case "$#" in
+ 0)
+ ;; # commit as-is
+ *)
+ if test -f "$GIT_DIR/MERGE_HEAD"
+ then
+ refuse_partial "Cannot do a partial commit during a merge."
+ fi
+ TMP_INDEX="$GIT_DIR/tmp-index$$"
+ commit_only=`git ls-files --error-unmatch -- "$@"` || exit
+
+ # Build a temporary index and update the real index
+ # the same way.
+ if test -z "$initial_commit"
+ then
+ GIT_INDEX_FILE="$THIS_INDEX" \
+ git read-tree --index-output="$TMP_INDEX" -i -m HEAD
+ else
+ rm -f "$TMP_INDEX"
+ fi || exit
+
+ printf '%s\n' "$commit_only" |
+ GIT_INDEX_FILE="$TMP_INDEX" \
+ git update-index --add --remove --stdin &&
+
+ save_index &&
+ printf '%s\n' "$commit_only" |
+ (
+ GIT_INDEX_FILE="$NEXT_INDEX"
+ export GIT_INDEX_FILE
+ git update-index --remove --stdin
+ ) || exit
+ ;;
+ esac
+ ;;
+esac
+
+################################################################
+# If we do as-is commit, the index file will be THIS_INDEX,
+# otherwise NEXT_INDEX after we make this commit. We leave
+# the index as is if we abort.
+
+if test -f "$NEXT_INDEX"
+then
+ USE_INDEX="$NEXT_INDEX"
+else
+ USE_INDEX="$THIS_INDEX"
+fi
+
+case "$status_only" in
+t)
+ # This will silently fail in a read-only repository, which is
+ # what we want.
+ GIT_INDEX_FILE="$USE_INDEX" git update-index -q --unmerged --refresh
+ run_status
+ exit $?
+ ;;
+'')
+ GIT_INDEX_FILE="$USE_INDEX" git update-index -q --refresh || exit
+ ;;
+esac
+
+################################################################
+# Grab commit message, write out tree and make commit.
+
+if test t = "$verify" && test -x "$GIT_DIR"/hooks/pre-commit
+then
+ if test "$TMP_INDEX"
+ then
+ GIT_INDEX_FILE="$TMP_INDEX" "$GIT_DIR"/hooks/pre-commit
+ else
+ GIT_INDEX_FILE="$USE_INDEX" "$GIT_DIR"/hooks/pre-commit
+ fi || exit
+fi
+
+if test "$log_message" != ''
+then
+ printf '%s\n' "$log_message"
+elif test "$logfile" != ""
+then
+ if test "$logfile" = -
+ then
+ test -t 0 &&
+ echo >&2 "(reading log message from standard input)"
+ cat
+ else
+ cat <"$logfile"
+ fi
+elif test "$use_commit" != ""
+then
+ encoding=$(git config i18n.commitencoding || echo UTF-8)
+ git show -s --pretty=raw --encoding="$encoding" "$use_commit" |
+ sed -e '1,/^$/d' -e 's/^ //'
+elif test -f "$GIT_DIR/MERGE_MSG"
+then
+ cat "$GIT_DIR/MERGE_MSG"
+elif test -f "$GIT_DIR/SQUASH_MSG"
+then
+ cat "$GIT_DIR/SQUASH_MSG"
+elif test "$templatefile" != ""
+then
+ cat "$templatefile"
+fi | git stripspace >"$GIT_DIR"/COMMIT_EDITMSG
+
+case "$signoff" in
+t)
+ sign=$(git-var GIT_COMMITTER_IDENT | sed -e '
+ s/>.*/>/
+ s/^/Signed-off-by: /
+ ')
+ blank_before_signoff=
+ tail -n 1 "$GIT_DIR"/COMMIT_EDITMSG |
+ grep 'Signed-off-by:' >/dev/null || blank_before_signoff='
+'
+ tail -n 1 "$GIT_DIR"/COMMIT_EDITMSG |
+ grep "$sign"$ >/dev/null ||
+ printf '%s%s\n' "$blank_before_signoff" "$sign" \
+ >>"$GIT_DIR"/COMMIT_EDITMSG
+ ;;
+esac
+
+if test -f "$GIT_DIR/MERGE_HEAD" && test -z "$no_edit"; then
+ echo "#"
+ echo "# It looks like you may be committing a MERGE."
+ echo "# If this is not correct, please remove the file"
+ printf '%s\n' "# $GIT_DIR/MERGE_HEAD"
+ echo "# and try again"
+ echo "#"
+fi >>"$GIT_DIR"/COMMIT_EDITMSG
+
+# Author
+if test '' != "$use_commit"
+then
+ eval "$(get_author_ident_from_commit "$use_commit")"
+ export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_AUTHOR_DATE
+fi
+if test '' != "$force_author"
+then
+ GIT_AUTHOR_NAME=`expr "z$force_author" : 'z\(.*[^ ]\) *<.*'` &&
+ GIT_AUTHOR_EMAIL=`expr "z$force_author" : '.*\(<.*\)'` &&
+ test '' != "$GIT_AUTHOR_NAME" &&
+ test '' != "$GIT_AUTHOR_EMAIL" ||
+ die "malformed --author parameter"
+ export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL
+fi
+
+PARENTS="-p HEAD"
+if test -z "$initial_commit"
+then
+ rloga='commit'
+ if [ -f "$GIT_DIR/MERGE_HEAD" ]; then
+ rloga='commit (merge)'
+ PARENTS="-p HEAD "`sed -e 's/^/-p /' "$GIT_DIR/MERGE_HEAD"`
+ elif test -n "$amend"; then
+ rloga='commit (amend)'
+ PARENTS=$(git cat-file commit HEAD |
+ sed -n -e '/^$/q' -e 's/^parent /-p /p')
+ fi
+ current="$(git rev-parse --verify HEAD)"
+else
+ if [ -z "$(git ls-files)" ]; then
+ echo >&2 'nothing to commit (use "git add file1 file2" to include for commit)'
+ exit 1
+ fi
+ PARENTS=""
+ rloga='commit (initial)'
+ current=''
+fi
+set_reflog_action "$rloga"
+
+if test -z "$no_edit"
+then
+ {
+ echo ""
+ echo "# Please enter the commit message for your changes."
+ echo "# (Comment lines starting with '#' will not be included)"
+ test -z "$only_include_assumed" || echo "$only_include_assumed"
+ run_status
+ } >>"$GIT_DIR"/COMMIT_EDITMSG
+else
+ # we need to check if there is anything to commit
+ run_status >/dev/null
+fi
+if [ "$?" != "0" -a ! -f "$GIT_DIR/MERGE_HEAD" -a -z "$amend" ]
+then
+ rm -f "$GIT_DIR/COMMIT_EDITMSG" "$GIT_DIR/SQUASH_MSG"
+ run_status
+ exit 1
+fi
+
+case "$no_edit" in
+'')
+ git-var GIT_AUTHOR_IDENT > /dev/null || die
+ git-var GIT_COMMITTER_IDENT > /dev/null || die
+ git_editor "$GIT_DIR/COMMIT_EDITMSG"
+ ;;
+esac
+
+case "$verify" in
+t)
+ if test -x "$GIT_DIR"/hooks/commit-msg
+ then
+ "$GIT_DIR"/hooks/commit-msg "$GIT_DIR"/COMMIT_EDITMSG || exit
+ fi
+esac
+
+if test -z "$no_edit"
+then
+ sed -e '
+ /^diff --git a\/.*/{
+ s///
+ q
+ }
+ /^#/d
+ ' "$GIT_DIR"/COMMIT_EDITMSG
+else
+ cat "$GIT_DIR"/COMMIT_EDITMSG
+fi |
+git stripspace >"$GIT_DIR"/COMMIT_MSG
+
+# Test whether the commit message has any content we didn't supply.
+have_commitmsg=
+grep -v -i '^Signed-off-by' "$GIT_DIR"/COMMIT_MSG |
+ git stripspace > "$GIT_DIR"/COMMIT_BAREMSG
+
+# Is the commit message totally empty?
+if test -s "$GIT_DIR"/COMMIT_BAREMSG
+then
+ if test "$templatefile" != ""
+ then
+ # Test whether this is just the unaltered template.
+ if cnt=`sed -e '/^#/d' < "$templatefile" |
+ git stripspace |
+ diff "$GIT_DIR"/COMMIT_BAREMSG - |
+ wc -l` &&
+ test 0 -lt $cnt
+ then
+ have_commitmsg=t
+ fi
+ else
+ # No template, so the content in the commit message must
+ # have come from the user.
+ have_commitmsg=t
+ fi
+fi
+
+rm -f "$GIT_DIR"/COMMIT_BAREMSG
+
+if test "$have_commitmsg" = "t"
+then
+ if test -z "$TMP_INDEX"
+ then
+ tree=$(GIT_INDEX_FILE="$USE_INDEX" git write-tree)
+ else
+ tree=$(GIT_INDEX_FILE="$TMP_INDEX" git write-tree) &&
+ rm -f "$TMP_INDEX"
+ fi &&
+ commit=$(git commit-tree $tree $PARENTS <"$GIT_DIR/COMMIT_MSG") &&
+ rlogm=$(sed -e 1q "$GIT_DIR"/COMMIT_MSG) &&
+ git update-ref -m "$GIT_REFLOG_ACTION: $rlogm" HEAD $commit "$current" &&
+ rm -f -- "$GIT_DIR/MERGE_HEAD" "$GIT_DIR/MERGE_MSG" &&
+ if test -f "$NEXT_INDEX"
+ then
+ mv "$NEXT_INDEX" "$THIS_INDEX"
+ else
+ : ;# happy
+ fi
+else
+ echo >&2 "* no commit message? aborting commit."
+ false
+fi
+ret="$?"
+rm -f "$GIT_DIR/COMMIT_MSG" "$GIT_DIR/COMMIT_EDITMSG" "$GIT_DIR/SQUASH_MSG"
+
+cd_to_toplevel
+
+git rerere
+
+if test "$ret" = 0
+then
+ if test -x "$GIT_DIR"/hooks/post-commit
+ then
+ "$GIT_DIR"/hooks/post-commit
+ fi
+ if test -z "$quiet"
+ then
+ commit=`git diff-tree --always --shortstat --pretty="format:%h: %s"\
+ --summary --root HEAD --`
+ echo "Created${initial_commit:+ initial} commit $commit"
+ fi
+fi
+
+exit "$ret"
diff --git a/git-commit.sh b/git-commit.sh
deleted file mode 100755
index d7e7028..0000000
--- a/git-commit.sh
+++ /dev/null
@@ -1,665 +0,0 @@
-#!/bin/sh
-#
-# Copyright (c) 2005 Linus Torvalds
-# Copyright (c) 2006 Junio C Hamano
-
-USAGE='[-a | --interactive] [-s] [-v] [--no-verify] [-m <message> | -F <logfile> | (-C|-c) <commit> | --amend] [-u] [-e] [--author <author>] [--template <file>] [[-i | -o] <path>...]'
-SUBDIRECTORY_OK=Yes
-. git-sh-setup
-require_work_tree
-
-git rev-parse --verify HEAD >/dev/null 2>&1 || initial_commit=t
-
-case "$0" in
-*status)
- status_only=t
- ;;
-*commit)
- status_only=
- ;;
-esac
-
-refuse_partial () {
- echo >&2 "$1"
- echo >&2 "You might have meant to say 'git commit -i paths...', perhaps?"
- exit 1
-}
-
-THIS_INDEX="$GIT_DIR/index"
-NEXT_INDEX="$GIT_DIR/next-index$$"
-rm -f "$NEXT_INDEX"
-save_index () {
- cp -p "$THIS_INDEX" "$NEXT_INDEX"
-}
-
-run_status () {
- # If TMP_INDEX is defined, that means we are doing
- # "--only" partial commit, and that index file is used
- # to build the tree for the commit. Otherwise, if
- # NEXT_INDEX exists, that is the index file used to
- # make the commit. Otherwise we are using as-is commit
- # so the regular index file is what we use to compare.
- if test '' != "$TMP_INDEX"
- then
- GIT_INDEX_FILE="$TMP_INDEX"
- export GIT_INDEX_FILE
- elif test -f "$NEXT_INDEX"
- then
- GIT_INDEX_FILE="$NEXT_INDEX"
- export GIT_INDEX_FILE
- fi
-
- case "$status_only" in
- t) color= ;;
- *) color=--nocolor ;;
- esac
- git runstatus ${color} \
- ${verbose:+--verbose} \
- ${amend:+--amend} \
- ${untracked_files:+--untracked}
-}
-
-trap '
- test -z "$TMP_INDEX" || {
- test -f "$TMP_INDEX" && rm -f "$TMP_INDEX"
- }
- rm -f "$NEXT_INDEX"
-' 0
-
-################################################################
-# Command line argument parsing and sanity checking
-
-all=
-also=
-interactive=
-only=
-logfile=
-use_commit=
-amend=
-edit_flag=
-no_edit=
-log_given=
-log_message=
-verify=t
-quiet=
-verbose=
-signoff=
-force_author=
-only_include_assumed=
-untracked_files=
-templatefile="`git config commit.template`"
-while case "$#" in 0) break;; esac
-do
- case "$1" in
- -F|--F|-f|--f|--fi|--fil|--file)
- case "$#" in 1) usage ;; esac
- shift
- no_edit=t
- log_given=t$log_given
- logfile="$1"
- shift
- ;;
- -F*|-f*)
- no_edit=t
- log_given=t$log_given
- logfile=`expr "z$1" : 'z-[Ff]\(.*\)'`
- shift
- ;;
- --F=*|--f=*|--fi=*|--fil=*|--file=*)
- no_edit=t
- log_given=t$log_given
- logfile=`expr "z$1" : 'z-[^=]*=\(.*\)'`
- shift
- ;;
- -a|--a|--al|--all)
- all=t
- shift
- ;;
- --au=*|--aut=*|--auth=*|--autho=*|--author=*)
- force_author=`expr "z$1" : 'z-[^=]*=\(.*\)'`
- shift
- ;;
- --au|--aut|--auth|--autho|--author)
- case "$#" in 1) usage ;; esac
- shift
- force_author="$1"
- shift
- ;;
- -e|--e|--ed|--edi|--edit)
- edit_flag=t
- shift
- ;;
- -i|--i|--in|--inc|--incl|--inclu|--includ|--include)
- also=t
- shift
- ;;
- --int|--inte|--inter|--intera|--interac|--interact|--interacti|\
- --interactiv|--interactive)
- interactive=t
- shift
- ;;
- -o|--o|--on|--onl|--only)
- only=t
- shift
- ;;
- -m|--m|--me|--mes|--mess|--messa|--messag|--message)
- case "$#" in 1) usage ;; esac
- shift
- log_given=m$log_given
- if test "$log_message" = ''
- then
- log_message="$1"
- else
- log_message="$log_message
-
-$1"
- fi
- no_edit=t
- shift
- ;;
- -m*)
- log_given=m$log_given
- if test "$log_message" = ''
- then
- log_message=`expr "z$1" : 'z-m\(.*\)'`
- else
- log_message="$log_message
-
-`expr "z$1" : 'z-m\(.*\)'`"
- fi
- no_edit=t
- shift
- ;;
- --m=*|--me=*|--mes=*|--mess=*|--messa=*|--messag=*|--message=*)
- log_given=m$log_given
- if test "$log_message" = ''
- then
- log_message=`expr "z$1" : 'z-[^=]*=\(.*\)'`
- else
- log_message="$log_message
-
-`expr "z$1" : 'zq-[^=]*=\(.*\)'`"
- fi
- no_edit=t
- shift
- ;;
- -n|--n|--no|--no-|--no-v|--no-ve|--no-ver|--no-veri|--no-verif|\
- --no-verify)
- verify=
- shift
- ;;
- --a|--am|--ame|--amen|--amend)
- amend=t
- use_commit=HEAD
- shift
- ;;
- -c)
- case "$#" in 1) usage ;; esac
- shift
- log_given=t$log_given
- use_commit="$1"
- no_edit=
- shift
- ;;
- --ree=*|--reed=*|--reedi=*|--reedit=*|--reedit-=*|--reedit-m=*|\
- --reedit-me=*|--reedit-mes=*|--reedit-mess=*|--reedit-messa=*|\
- --reedit-messag=*|--reedit-message=*)
- log_given=t$log_given
- use_commit=`expr "z$1" : 'z-[^=]*=\(.*\)'`
- no_edit=
- shift
- ;;
- --ree|--reed|--reedi|--reedit|--reedit-|--reedit-m|--reedit-me|\
- --reedit-mes|--reedit-mess|--reedit-messa|--reedit-messag|\
- --reedit-message)
- case "$#" in 1) usage ;; esac
- shift
- log_given=t$log_given
- use_commit="$1"
- no_edit=
- shift
- ;;
- -C)
- case "$#" in 1) usage ;; esac
- shift
- log_given=t$log_given
- use_commit="$1"
- no_edit=t
- shift
- ;;
- --reu=*|--reus=*|--reuse=*|--reuse-=*|--reuse-m=*|--reuse-me=*|\
- --reuse-mes=*|--reuse-mess=*|--reuse-messa=*|--reuse-messag=*|\
- --reuse-message=*)
- log_given=t$log_given
- use_commit=`expr "z$1" : 'z-[^=]*=\(.*\)'`
- no_edit=t
- shift
- ;;
- --reu|--reus|--reuse|--reuse-|--reuse-m|--reuse-me|--reuse-mes|\
- --reuse-mess|--reuse-messa|--reuse-messag|--reuse-message)
- case "$#" in 1) usage ;; esac
- shift
- log_given=t$log_given
- use_commit="$1"
- no_edit=t
- shift
- ;;
- -s|--s|--si|--sig|--sign|--signo|--signof|--signoff)
- signoff=t
- shift
- ;;
- -t|--t|--te|--tem|--temp|--templ|--templa|--templat|--template)
- case "$#" in 1) usage ;; esac
- shift
- templatefile="$1"
- no_edit=
- shift
- ;;
- -q|--q|--qu|--qui|--quie|--quiet)
- quiet=t
- shift
- ;;
- -v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose)
- verbose=t
- shift
- ;;
- -u|--u|--un|--unt|--untr|--untra|--untrac|--untrack|--untracke|\
- --untracked|--untracked-|--untracked-f|--untracked-fi|--untracked-fil|\
- --untracked-file|--untracked-files)
- untracked_files=t
- shift
- ;;
- --)
- shift
- break
- ;;
- -*)
- usage
- ;;
- *)
- break
- ;;
- esac
-done
-case "$edit_flag" in t) no_edit= ;; esac
-
-################################################################
-# Sanity check options
-
-case "$amend,$initial_commit" in
-t,t)
- die "You do not have anything to amend." ;;
-t,)
- if [ -f "$GIT_DIR/MERGE_HEAD" ]; then
- die "You are in the middle of a merge -- cannot amend."
- fi ;;
-esac
-
-case "$log_given" in
-tt*)
- die "Only one of -c/-C/-F can be used." ;;
-*tm*|*mt*)
- die "Option -m cannot be combined with -c/-C/-F." ;;
-esac
-
-case "$#,$also,$only,$amend" in
-*,t,t,*)
- die "Only one of --include/--only can be used." ;;
-0,t,,* | 0,,t,)
- die "No paths with --include/--only does not make sense." ;;
-0,,t,t)
- only_include_assumed="# Clever... amending the last one with dirty index." ;;
-0,,,*)
- ;;
-*,,,*)
- only_include_assumed="# Explicit paths specified without -i nor -o; assuming --only paths..."
- also=
- ;;
-esac
-unset only
-case "$all,$interactive,$also,$#" in
-*t,*t,*)
- die "Cannot use -a, --interactive or -i at the same time." ;;
-t,,[1-9]*)
- die "Paths with -a does not make sense." ;;
-,t,[1-9]*)
- die "Paths with --interactive does not make sense." ;;
-,,t,0)
- die "No paths with -i does not make sense." ;;
-esac
-
-if test ! -z "$templatefile" -a -z "$log_given"
-then
- if test ! -f "$templatefile"
- then
- die "Commit template file does not exist."
- fi
-fi
-
-################################################################
-# Prepare index to have a tree to be committed
-
-case "$all,$also" in
-t,)
- if test ! -f "$THIS_INDEX"
- then
- die 'nothing to commit (use "git add file1 file2" to include for commit)'
- fi
- save_index &&
- (
- cd_to_toplevel &&
- GIT_INDEX_FILE="$NEXT_INDEX" &&
- export GIT_INDEX_FILE &&
- git diff-files --name-only -z |
- git update-index --remove -z --stdin
- ) || exit
- ;;
-,t)
- save_index &&
- git ls-files --error-unmatch -- "$@" >/dev/null || exit
-
- git diff-files --name-only -z -- "$@" |
- (
- cd_to_toplevel &&
- GIT_INDEX_FILE="$NEXT_INDEX" &&
- export GIT_INDEX_FILE &&
- git update-index --remove -z --stdin
- ) || exit
- ;;
-,)
- if test "$interactive" = t; then
- git add --interactive || exit
- fi
- case "$#" in
- 0)
- ;; # commit as-is
- *)
- if test -f "$GIT_DIR/MERGE_HEAD"
- then
- refuse_partial "Cannot do a partial commit during a merge."
- fi
- TMP_INDEX="$GIT_DIR/tmp-index$$"
- commit_only=`git ls-files --error-unmatch -- "$@"` || exit
-
- # Build a temporary index and update the real index
- # the same way.
- if test -z "$initial_commit"
- then
- GIT_INDEX_FILE="$THIS_INDEX" \
- git read-tree --index-output="$TMP_INDEX" -i -m HEAD
- else
- rm -f "$TMP_INDEX"
- fi || exit
-
- printf '%s\n' "$commit_only" |
- GIT_INDEX_FILE="$TMP_INDEX" \
- git update-index --add --remove --stdin &&
-
- save_index &&
- printf '%s\n' "$commit_only" |
- (
- GIT_INDEX_FILE="$NEXT_INDEX"
- export GIT_INDEX_FILE
- git update-index --remove --stdin
- ) || exit
- ;;
- esac
- ;;
-esac
-
-################################################################
-# If we do as-is commit, the index file will be THIS_INDEX,
-# otherwise NEXT_INDEX after we make this commit. We leave
-# the index as is if we abort.
-
-if test -f "$NEXT_INDEX"
-then
- USE_INDEX="$NEXT_INDEX"
-else
- USE_INDEX="$THIS_INDEX"
-fi
-
-case "$status_only" in
-t)
- # This will silently fail in a read-only repository, which is
- # what we want.
- GIT_INDEX_FILE="$USE_INDEX" git update-index -q --unmerged --refresh
- run_status
- exit $?
- ;;
-'')
- GIT_INDEX_FILE="$USE_INDEX" git update-index -q --refresh || exit
- ;;
-esac
-
-################################################################
-# Grab commit message, write out tree and make commit.
-
-if test t = "$verify" && test -x "$GIT_DIR"/hooks/pre-commit
-then
- if test "$TMP_INDEX"
- then
- GIT_INDEX_FILE="$TMP_INDEX" "$GIT_DIR"/hooks/pre-commit
- else
- GIT_INDEX_FILE="$USE_INDEX" "$GIT_DIR"/hooks/pre-commit
- fi || exit
-fi
-
-if test "$log_message" != ''
-then
- printf '%s\n' "$log_message"
-elif test "$logfile" != ""
-then
- if test "$logfile" = -
- then
- test -t 0 &&
- echo >&2 "(reading log message from standard input)"
- cat
- else
- cat <"$logfile"
- fi
-elif test "$use_commit" != ""
-then
- encoding=$(git config i18n.commitencoding || echo UTF-8)
- git show -s --pretty=raw --encoding="$encoding" "$use_commit" |
- sed -e '1,/^$/d' -e 's/^ //'
-elif test -f "$GIT_DIR/MERGE_MSG"
-then
- cat "$GIT_DIR/MERGE_MSG"
-elif test -f "$GIT_DIR/SQUASH_MSG"
-then
- cat "$GIT_DIR/SQUASH_MSG"
-elif test "$templatefile" != ""
-then
- cat "$templatefile"
-fi | git stripspace >"$GIT_DIR"/COMMIT_EDITMSG
-
-case "$signoff" in
-t)
- sign=$(git-var GIT_COMMITTER_IDENT | sed -e '
- s/>.*/>/
- s/^/Signed-off-by: /
- ')
- blank_before_signoff=
- tail -n 1 "$GIT_DIR"/COMMIT_EDITMSG |
- grep 'Signed-off-by:' >/dev/null || blank_before_signoff='
-'
- tail -n 1 "$GIT_DIR"/COMMIT_EDITMSG |
- grep "$sign"$ >/dev/null ||
- printf '%s%s\n' "$blank_before_signoff" "$sign" \
- >>"$GIT_DIR"/COMMIT_EDITMSG
- ;;
-esac
-
-if test -f "$GIT_DIR/MERGE_HEAD" && test -z "$no_edit"; then
- echo "#"
- echo "# It looks like you may be committing a MERGE."
- echo "# If this is not correct, please remove the file"
- printf '%s\n' "# $GIT_DIR/MERGE_HEAD"
- echo "# and try again"
- echo "#"
-fi >>"$GIT_DIR"/COMMIT_EDITMSG
-
-# Author
-if test '' != "$use_commit"
-then
- eval "$(get_author_ident_from_commit "$use_commit")"
- export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_AUTHOR_DATE
-fi
-if test '' != "$force_author"
-then
- GIT_AUTHOR_NAME=`expr "z$force_author" : 'z\(.*[^ ]\) *<.*'` &&
- GIT_AUTHOR_EMAIL=`expr "z$force_author" : '.*\(<.*\)'` &&
- test '' != "$GIT_AUTHOR_NAME" &&
- test '' != "$GIT_AUTHOR_EMAIL" ||
- die "malformed --author parameter"
- export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL
-fi
-
-PARENTS="-p HEAD"
-if test -z "$initial_commit"
-then
- rloga='commit'
- if [ -f "$GIT_DIR/MERGE_HEAD" ]; then
- rloga='commit (merge)'
- PARENTS="-p HEAD "`sed -e 's/^/-p /' "$GIT_DIR/MERGE_HEAD"`
- elif test -n "$amend"; then
- rloga='commit (amend)'
- PARENTS=$(git cat-file commit HEAD |
- sed -n -e '/^$/q' -e 's/^parent /-p /p')
- fi
- current="$(git rev-parse --verify HEAD)"
-else
- if [ -z "$(git ls-files)" ]; then
- echo >&2 'nothing to commit (use "git add file1 file2" to include for commit)'
- exit 1
- fi
- PARENTS=""
- rloga='commit (initial)'
- current=''
-fi
-set_reflog_action "$rloga"
-
-if test -z "$no_edit"
-then
- {
- echo ""
- echo "# Please enter the commit message for your changes."
- echo "# (Comment lines starting with '#' will not be included)"
- test -z "$only_include_assumed" || echo "$only_include_assumed"
- run_status
- } >>"$GIT_DIR"/COMMIT_EDITMSG
-else
- # we need to check if there is anything to commit
- run_status >/dev/null
-fi
-if [ "$?" != "0" -a ! -f "$GIT_DIR/MERGE_HEAD" -a -z "$amend" ]
-then
- rm -f "$GIT_DIR/COMMIT_EDITMSG" "$GIT_DIR/SQUASH_MSG"
- run_status
- exit 1
-fi
-
-case "$no_edit" in
-'')
- git-var GIT_AUTHOR_IDENT > /dev/null || die
- git-var GIT_COMMITTER_IDENT > /dev/null || die
- git_editor "$GIT_DIR/COMMIT_EDITMSG"
- ;;
-esac
-
-case "$verify" in
-t)
- if test -x "$GIT_DIR"/hooks/commit-msg
- then
- "$GIT_DIR"/hooks/commit-msg "$GIT_DIR"/COMMIT_EDITMSG || exit
- fi
-esac
-
-if test -z "$no_edit"
-then
- sed -e '
- /^diff --git a\/.*/{
- s///
- q
- }
- /^#/d
- ' "$GIT_DIR"/COMMIT_EDITMSG
-else
- cat "$GIT_DIR"/COMMIT_EDITMSG
-fi |
-git stripspace >"$GIT_DIR"/COMMIT_MSG
-
-# Test whether the commit message has any content we didn't supply.
-have_commitmsg=
-grep -v -i '^Signed-off-by' "$GIT_DIR"/COMMIT_MSG |
- git stripspace > "$GIT_DIR"/COMMIT_BAREMSG
-
-# Is the commit message totally empty?
-if test -s "$GIT_DIR"/COMMIT_BAREMSG
-then
- if test "$templatefile" != ""
- then
- # Test whether this is just the unaltered template.
- if cnt=`sed -e '/^#/d' < "$templatefile" |
- git stripspace |
- diff "$GIT_DIR"/COMMIT_BAREMSG - |
- wc -l` &&
- test 0 -lt $cnt
- then
- have_commitmsg=t
- fi
- else
- # No template, so the content in the commit message must
- # have come from the user.
- have_commitmsg=t
- fi
-fi
-
-rm -f "$GIT_DIR"/COMMIT_BAREMSG
-
-if test "$have_commitmsg" = "t"
-then
- if test -z "$TMP_INDEX"
- then
- tree=$(GIT_INDEX_FILE="$USE_INDEX" git write-tree)
- else
- tree=$(GIT_INDEX_FILE="$TMP_INDEX" git write-tree) &&
- rm -f "$TMP_INDEX"
- fi &&
- commit=$(git commit-tree $tree $PARENTS <"$GIT_DIR/COMMIT_MSG") &&
- rlogm=$(sed -e 1q "$GIT_DIR"/COMMIT_MSG) &&
- git update-ref -m "$GIT_REFLOG_ACTION: $rlogm" HEAD $commit "$current" &&
- rm -f -- "$GIT_DIR/MERGE_HEAD" "$GIT_DIR/MERGE_MSG" &&
- if test -f "$NEXT_INDEX"
- then
- mv "$NEXT_INDEX" "$THIS_INDEX"
- else
- : ;# happy
- fi
-else
- echo >&2 "* no commit message? aborting commit."
- false
-fi
-ret="$?"
-rm -f "$GIT_DIR/COMMIT_MSG" "$GIT_DIR/COMMIT_EDITMSG" "$GIT_DIR/SQUASH_MSG"
-
-cd_to_toplevel
-
-git rerere
-
-if test "$ret" = 0
-then
- if test -x "$GIT_DIR"/hooks/post-commit
- then
- "$GIT_DIR"/hooks/post-commit
- fi
- if test -z "$quiet"
- then
- commit=`git diff-tree --always --shortstat --pretty="format:%h: %s"\
- --summary --root HEAD --`
- echo "Created${initial_commit:+ initial} commit $commit"
- fi
-fi
-
-exit "$ret"
diff --git a/git.c b/git.c
index cab0e72..62859a2 100644
--- a/git.c
+++ b/git.c
@@ -321,6 +321,7 @@ static void handle_internal_command(int argc, const char **argv)
{ "check-attr", cmd_check_attr, RUN_SETUP | NEED_WORK_TREE },
{ "cherry", cmd_cherry, RUN_SETUP },
{ "cherry-pick", cmd_cherry_pick, RUN_SETUP | NEED_WORK_TREE },
+ { "commit", cmd_commit, RUN_SETUP | NEED_WORK_TREE },
{ "commit-tree", cmd_commit_tree, RUN_SETUP },
{ "config", cmd_config },
{ "count-objects", cmd_count_objects, RUN_SETUP },
@@ -363,10 +364,10 @@ static void handle_internal_command(int argc, const char **argv)
{ "rev-parse", cmd_rev_parse, RUN_SETUP },
{ "revert", cmd_revert, RUN_SETUP | NEED_WORK_TREE },
{ "rm", cmd_rm, RUN_SETUP | NEED_WORK_TREE },
- { "runstatus", cmd_runstatus, RUN_SETUP | NEED_WORK_TREE },
{ "shortlog", cmd_shortlog, RUN_SETUP | USE_PAGER },
{ "show-branch", cmd_show_branch, RUN_SETUP },
{ "show", cmd_show, RUN_SETUP | USE_PAGER },
+ { "status", cmd_status, RUN_SETUP | NEED_WORK_TREE },
{ "stripspace", cmd_stripspace },
{ "symbolic-ref", cmd_symbolic_ref, RUN_SETUP },
{ "tag", cmd_tag, RUN_SETUP },
--
1.5.2.GIT
^ permalink raw reply related
* [PATCH 7/9] Add strbuf_read_path().
From: Kristian Høgsberg @ 2007-09-06 0:23 UTC (permalink / raw)
To: git; +Cc: Kristian Høgsberg
In-Reply-To: <1189038225525-git-send-email-krh@redhat.com>
Signed-off-by: Kristian Høgsberg <krh@redhat.com>
---
builtin-tag.c | 7 +------
strbuf.c | 15 +++++++++++++++
strbuf.h | 1 +
3 files changed, 17 insertions(+), 6 deletions(-)
diff --git a/builtin-tag.c b/builtin-tag.c
index 1aff952..8724d49 100644
--- a/builtin-tag.c
+++ b/builtin-tag.c
@@ -53,14 +53,9 @@ static void launch_editor(const char *path, struct strbuf *sb)
if (run_command(&child))
die("There was a problem with the editor %s.", editor);
- fd = open(path, O_RDONLY);
- if (fd < 0)
- die("could not open '%s': %s", path, strerror(errno));
- if (strbuf_read_fd(sb, fd) < 0) {
+ if (strbuf_read_path(sb, path) < 0)
die("could not read message file '%s': %s",
path, strerror(errno));
- }
- close(fd);
}
struct tag_filter {
diff --git a/strbuf.c b/strbuf.c
index ed2afea..1951a5b 100644
--- a/strbuf.c
+++ b/strbuf.c
@@ -70,6 +70,21 @@ int strbuf_read_fd(struct strbuf *sb, int fd)
return total;
}
+int strbuf_read_path(struct strbuf *sb, const char *path)
+{
+ int fd, len;
+
+ fd = open(path, O_RDONLY);
+ if (fd < 0)
+ return -1;
+ len = strbuf_read_fd(sb, fd);
+ if (len < 0)
+ return -1;
+ close(fd);
+
+ return len;
+}
+
void strbuf_printf(struct strbuf *sb, const char *fmt, ...)
{
char buffer[2048];
diff --git a/strbuf.h b/strbuf.h
index a93b9e1..e852070 100644
--- a/strbuf.h
+++ b/strbuf.h
@@ -14,6 +14,7 @@ extern void strbuf_add(struct strbuf *sb, const char *data, size_t len);
extern void strbuf_add_char(struct strbuf *sb, int ch);
extern void strbuf_printf(struct strbuf *sb, const char *fmt, ...);
extern int strbuf_read_fd(struct strbuf *sb, int fd);
+extern int strbuf_read_path(struct strbuf *sb, const char *path);
extern size_t stripspace(struct strbuf *sb, int skip_comments);
#endif /* STRBUF_H */
--
1.5.2.GIT
^ permalink raw reply related
* [PATCH 8/9] Export rerere() and launch_editor().
From: Kristian Høgsberg @ 2007-09-06 0:23 UTC (permalink / raw)
To: git; +Cc: Kristian Høgsberg
In-Reply-To: <11890382262161-git-send-email-krh@redhat.com>
Signed-off-by: Kristian Høgsberg <krh@redhat.com>
---
builtin-rerere.c | 16 ++++++++++++++++
builtin-tag.c | 3 +--
commit.h | 1 +
strbuf.h | 1 +
4 files changed, 19 insertions(+), 2 deletions(-)
diff --git a/builtin-rerere.c b/builtin-rerere.c
index 29d057c..eb22a28 100644
--- a/builtin-rerere.c
+++ b/builtin-rerere.c
@@ -415,6 +415,22 @@ static int is_rerere_enabled(void)
return 1;
}
+/* Export for builtin-commit. */
+int rerere(void)
+{
+ struct path_list merge_rr = { NULL, 0, 0, 1 };
+ int fd;
+
+ git_config(git_rerere_config);
+ if (!is_rerere_enabled())
+ return 0;
+
+ merge_rr_path = xstrdup(git_path("rr-cache/MERGE_RR"));
+ fd = hold_lock_file_for_update(&write_lock, merge_rr_path, 1);
+ read_rr(&merge_rr);
+ return do_plain_rerere(&merge_rr, fd);
+}
+
int cmd_rerere(int argc, const char **argv, const char *prefix)
{
struct path_list merge_rr = { NULL, 0, 0, 1 };
diff --git a/builtin-tag.c b/builtin-tag.c
index 8724d49..3b181f5 100644
--- a/builtin-tag.c
+++ b/builtin-tag.c
@@ -18,12 +18,11 @@ static const char builtin_tag_usage[] =
static char signingkey[1000];
-static void launch_editor(const char *path, struct strbuf *sb)
+void launch_editor(const char *path, struct strbuf *sb)
{
const char *editor, *terminal;
struct child_process child;
const char *args[3];
- int fd;
editor = getenv("GIT_EDITOR");
if (!editor && editor_program)
diff --git a/commit.h b/commit.h
index 64e1d4b..39934cc 100644
--- a/commit.h
+++ b/commit.h
@@ -130,5 +130,6 @@ create_commit(const unsigned char *tree_sha1,
const char *message, int length);
extern int interactive_add(void);
+extern int rerere(void);
#endif /* COMMIT_H */
diff --git a/strbuf.h b/strbuf.h
index e852070..b009b4b 100644
--- a/strbuf.h
+++ b/strbuf.h
@@ -16,5 +16,6 @@ extern void strbuf_printf(struct strbuf *sb, const char *fmt, ...);
extern int strbuf_read_fd(struct strbuf *sb, int fd);
extern int strbuf_read_path(struct strbuf *sb, const char *path);
extern size_t stripspace(struct strbuf *sb, int skip_comments);
+extern void launch_editor(const char *path, struct strbuf *sb);
#endif /* STRBUF_H */
--
1.5.2.GIT
^ permalink raw reply related
* [PATCH 6/9] Rewrite launch_editor, create_tag and stripspace to use strbufs.
From: Kristian Høgsberg @ 2007-09-06 0:23 UTC (permalink / raw)
To: git; +Cc: Kristian Høgsberg
In-Reply-To: <11890382252522-git-send-email-krh@redhat.com>
Signed-off-by: Kristian Høgsberg <krh@redhat.com>
---
builtin-stripspace.c | 45 +++++++++++++-------------
builtin-tag.c | 84 +++++++++++++++++++++-----------------------------
builtin.h | 1 -
strbuf.c | 43 +++++---------------------
strbuf.h | 2 +
5 files changed, 68 insertions(+), 107 deletions(-)
diff --git a/builtin-stripspace.c b/builtin-stripspace.c
index 916355c..f0264a8 100644
--- a/builtin-stripspace.c
+++ b/builtin-stripspace.c
@@ -1,5 +1,6 @@
#include "builtin.h"
#include "cache.h"
+#include "strbuf.h"
/*
* Returns the length of a line, without trailing spaces.
@@ -34,34 +35,34 @@ static size_t cleanup(char *line, size_t len)
* If the input has only empty lines and spaces,
* no output will be produced.
*
- * If last line has a newline at the end, it will be removed.
+ * If the last line does not have a newline, one will be added.
*
* Enable skip_comments to skip every line starting with "#".
*/
-size_t stripspace(char *buffer, size_t length, int skip_comments)
+size_t stripspace(struct strbuf *sb, int skip_comments)
{
int empties = -1;
size_t i, j, len, newlen;
char *eol;
- for (i = j = 0; i < length; i += len, j += newlen) {
- eol = memchr(buffer + i, '\n', length - i);
- len = eol ? eol - (buffer + i) + 1 : length - i;
+ for (i = j = 0; i < sb->len; i += len, j += newlen) {
+ eol = memchr(sb->buf + i, '\n', sb->len - i);
+ len = eol ? eol - (sb->buf + i) + 1 : sb->len - i;
- if (skip_comments && len && buffer[i] == '#') {
+ if (skip_comments && len && sb->buf[i] == '#') {
newlen = 0;
continue;
}
- newlen = cleanup(buffer + i, len);
+ newlen = cleanup(sb->buf + i, len);
/* Not just an empty line? */
if (newlen) {
if (empties != -1)
- buffer[j++] = '\n';
+ sb->buf[j++] = '\n';
if (empties > 0)
- buffer[j++] = '\n';
+ sb->buf[j++] = '\n';
empties = 0;
- memmove(buffer + j, buffer + i, newlen);
+ memmove(sb->buf + j, sb->buf + i, newlen);
continue;
}
if (empties < 0)
@@ -69,31 +70,31 @@ size_t stripspace(char *buffer, size_t length, int skip_comments)
empties++;
}
- return j;
+ sb->len = j;
+ if (j > 0)
+ strbuf_add_char(sb, '\n');
+
+ return sb->len;
}
int cmd_stripspace(int argc, const char **argv, const char *prefix)
{
- char *buffer;
- unsigned long size;
+ struct strbuf sb;
int strip_comments = 0;
if (argc > 1 && (!strcmp(argv[1], "-s") ||
!strcmp(argv[1], "--strip-comments")))
strip_comments = 1;
- size = 1024;
- buffer = xmalloc(size);
- if (read_fd(0, &buffer, &size)) {
- free(buffer);
+ strbuf_init(&sb);
+ if (strbuf_read_fd(&sb, 0) < 0) {
+ strbuf_release(&sb);
die("could not read the input");
}
- size = stripspace(buffer, size, strip_comments);
- write_or_die(1, buffer, size);
- if (size)
- putc('\n', stdout);
+ stripspace(&sb, strip_comments);
+ write_or_die(1, sb.buf, sb.len);
+ strbuf_release(&sb);
- free(buffer);
return 0;
}
diff --git a/builtin-tag.c b/builtin-tag.c
index d6d38ad..1aff952 100644
--- a/builtin-tag.c
+++ b/builtin-tag.c
@@ -11,13 +11,14 @@
#include "refs.h"
#include "tag.h"
#include "run-command.h"
+#include "strbuf.h"
static const char builtin_tag_usage[] =
"git-tag [-n [<num>]] -l [<pattern>] | [-a | -s | -u <key-id>] [-f | -d | -v] [-m <msg> | -F <file>] <tagname> [<head>]";
static char signingkey[1000];
-static void launch_editor(const char *path, char **buffer, unsigned long *len)
+static void launch_editor(const char *path, struct strbuf *sb)
{
const char *editor, *terminal;
struct child_process child;
@@ -55,10 +56,9 @@ static void launch_editor(const char *path, char **buffer, unsigned long *len)
fd = open(path, O_RDONLY);
if (fd < 0)
die("could not open '%s': %s", path, strerror(errno));
- if (read_fd(fd, buffer, len)) {
- free(*buffer);
+ if (strbuf_read_fd(sb, fd) < 0) {
die("could not read message file '%s': %s",
- path, strerror(errno));
+ path, strerror(errno));
}
close(fd);
}
@@ -191,7 +191,7 @@ static int verify_tag(const char *name, const char *ref,
return 0;
}
-static ssize_t do_sign(char *buffer, size_t size, size_t max)
+static int do_sign(struct strbuf *sb)
{
struct child_process gpg;
const char *args[4];
@@ -219,17 +219,13 @@ static ssize_t do_sign(char *buffer, size_t size, size_t max)
if (start_command(&gpg))
return error("could not run gpg.");
- write_or_die(gpg.in, buffer, size);
+ write_or_die(gpg.in, sb->buf, sb->len);
close(gpg.in);
gpg.close_in = 0;
- len = read_in_full(gpg.out, buffer + size, max - size);
-
+ len = strbuf_read_fd(sb, gpg.out);
finish_command(&gpg);
- if (len == max - size)
- return error("could not read the entire signature from gpg.");
-
- return size + len;
+ return len;
}
static const char tag_template[] =
@@ -258,26 +254,24 @@ static void create_tag(const unsigned char *object, const char *tag,
char *message, int sign, unsigned char *result)
{
enum object_type type;
- char header_buf[1024], *buffer = NULL;
- int header_len, max_size;
- unsigned long size = 0;
+ struct strbuf sb;
+ int header_len;
type = sha1_object_info(object, NULL);
if (type <= OBJ_NONE)
die("bad object type.");
- header_len = snprintf(header_buf, sizeof(header_buf),
- "object %s\n"
- "type %s\n"
- "tag %s\n"
- "tagger %s\n\n",
- sha1_to_hex(object),
- typename(type),
- tag,
- git_committer_info(1));
-
- if (header_len > sizeof(header_buf) - 1)
- die("tag header too big.");
+ strbuf_init(&sb);
+ strbuf_printf(&sb,
+ "object %s\n"
+ "type %s\n"
+ "tag %s\n"
+ "tagger %s\n\n",
+ sha1_to_hex(object),
+ typename(type),
+ tag,
+ git_committer_info(1));
+ header_len = sb.len;
if (!message) {
char *path;
@@ -292,39 +286,31 @@ static void create_tag(const unsigned char *object, const char *tag,
write_or_die(fd, tag_template, strlen(tag_template));
close(fd);
- launch_editor(path, &buffer, &size);
+ launch_editor(path, &sb);
unlink(path);
free(path);
}
else {
- buffer = message;
- size = strlen(message);
+ strbuf_add(&sb, message, strlen(message));
}
- size = stripspace(buffer, size, 1);
-
- if (!message && !size)
+ if (stripspace(&sb, 1) <= header_len && !message)
die("no tag message?");
- /* insert the header and add the '\n' if needed: */
- max_size = header_len + size + (sign ? MAX_SIGNATURE_LENGTH : 0) + 1;
- buffer = xrealloc(buffer, max_size);
- if (size)
- buffer[size++] = '\n';
- memmove(buffer + header_len, buffer, size);
- memcpy(buffer, header_buf, header_len);
- size += header_len;
-
- if (sign) {
- size = do_sign(buffer, size, max_size);
- if (size < 0)
- die("unable to sign the tag");
- }
+ /* Debatable, but makes t7401-tag.sh pass. Creates an extra
+ * blank line after the header block for tags with empty
+ * messages. */
+ if (sb.len < header_len)
+ strbuf_add_char(&sb, '\n');
- if (write_sha1_file(buffer, size, tag_type, result) < 0)
+ if (sign && do_sign(&sb) < 0)
+ die("unable to sign the tag");
+
+ if (write_sha1_file(sb.buf, sb.len, tag_type, result) < 0)
die("unable to write tag file");
- free(buffer);
+
+ strbuf_release(&sb);
}
int cmd_tag(int argc, const char **argv, const char *prefix)
diff --git a/builtin.h b/builtin.h
index bb72000..91bc595 100644
--- a/builtin.h
+++ b/builtin.h
@@ -7,7 +7,6 @@ extern const char git_version_string[];
extern const char git_usage_string[];
extern void help_unknown_cmd(const char *cmd);
-extern size_t stripspace(char *buffer, size_t length, int skip_comments);
extern int write_tree(unsigned char *sha1, int missing_ok, const char *prefix);
extern void prune_packed_objects(int);
diff --git a/strbuf.c b/strbuf.c
index fcfc05e..ed2afea 100644
--- a/strbuf.c
+++ b/strbuf.c
@@ -6,7 +6,8 @@ void strbuf_init(struct strbuf *sb) {
sb->eof = sb->alloc = sb->len = 0;
}
-static void strbuf_begin(struct strbuf *sb) {
+void strbuf_release(struct strbuf *sb)
+{
free(sb->buf);
strbuf_init(sb);
}
@@ -35,7 +36,7 @@ static void strbuf_end(struct strbuf *sb) {
void read_line(struct strbuf *sb, FILE *fp, int term) {
int ch;
- strbuf_begin(sb);
+ strbuf_release(sb);
if (feof(fp)) {
sb->eof = 1;
return;
@@ -73,43 +74,15 @@ void strbuf_printf(struct strbuf *sb, const char *fmt, ...)
{
char buffer[2048];
va_list args;
- int len, size = 2 * sizeof buffer;
+ int len;
va_start(args, fmt);
len = vsnprintf(buffer, sizeof(buffer), fmt, args);
va_end(args);
- if (len > sizeof(buffer)) {
- /*
- * Didn't fit in the buffer, but this vsnprintf at
- * least gives us the required length back. Grow the
- * buffer acccordingly and try again.
- */
- strbuf_grow(sb, len);
- va_start(args, fmt);
- len = vsnprintf(sb->buf + sb->len,
- sb->alloc - sb->len, fmt, args);
- va_end(args);
- } else if (len >= 0) {
- /*
- * The initial vsnprintf fit in the temp buffer, just
- * copy it to the strbuf.
- */
- strbuf_add(sb, buffer, len);
- } else {
- /*
- * This vnsprintf sucks and just returns -1 when the
- * buffer is too small. Keep doubling the size until
- * it fits.
- */
- while (len < 0) {
- strbuf_grow(sb, size);
- va_start(args, fmt);
- len = vsnprintf(sb->buf + sb->len,
- sb->alloc - sb->len, fmt, args);
- va_end(args);
- size *= 2;
- }
- }
+ if (len > sizeof(buffer) || len < 0)
+ die("out of buffer space\n");
+
+ strbuf_add(sb, buffer, len);
}
diff --git a/strbuf.h b/strbuf.h
index 6e630ea..a93b9e1 100644
--- a/strbuf.h
+++ b/strbuf.h
@@ -8,10 +8,12 @@ struct strbuf {
};
extern void strbuf_init(struct strbuf *);
+extern void strbuf_release(struct strbuf *);
extern void read_line(struct strbuf *, FILE *, int);
extern void strbuf_add(struct strbuf *sb, const char *data, size_t len);
extern void strbuf_add_char(struct strbuf *sb, int ch);
extern void strbuf_printf(struct strbuf *sb, const char *fmt, ...);
extern int strbuf_read_fd(struct strbuf *sb, int fd);
+extern size_t stripspace(struct strbuf *sb, int skip_comments);
#endif /* STRBUF_H */
--
1.5.2.GIT
^ permalink raw reply related
* [PATCH 4/9] Introduce entry point for launching add--interactive.
From: Kristian Høgsberg @ 2007-09-06 0:23 UTC (permalink / raw)
To: git; +Cc: Kristian Høgsberg
In-Reply-To: <11890382243290-git-send-email-krh@redhat.com>
This refactors builtin-add.c a little to provide a unique entry point
for launching git add --interactive, which will be used by
builtin-commit too. If we later want to make add --interactive a
builtin or change how it is launched, we just start from this function.
Signed-off-by: Kristian Høgsberg <krh@redhat.com>
---
builtin-add.c | 15 ++++++++++-----
commit.h | 9 +++++++++
2 files changed, 19 insertions(+), 5 deletions(-)
diff --git a/builtin-add.c b/builtin-add.c
index 3dd4ded..e79e8f7 100644
--- a/builtin-add.c
+++ b/builtin-add.c
@@ -12,6 +12,7 @@
#include "diffcore.h"
#include "commit.h"
#include "revision.h"
+#include "run-command.h"
static const char builtin_add_usage[] =
"git-add [-n] [-v] [-f] [--interactive | -i] [-u] [--] <filepattern>...";
@@ -153,6 +154,13 @@ static int git_add_config(const char *var, const char *value)
return git_default_config(var, value);
}
+int interactive_add(void)
+{
+ const char *argv[2] = { "add--interactive", NULL };
+
+ return run_command_v_opt(argv, RUN_GIT_CMD);
+}
+
static struct lock_file lock_file;
static const char ignore_warning[] =
@@ -172,12 +180,9 @@ int cmd_add(int argc, const char **argv, const char *prefix)
add_interactive++;
}
if (add_interactive) {
- const char *args[] = { "add--interactive", NULL };
-
- if (add_interactive != 1 || argc != 2)
+ if (argc != 2)
die("add --interactive does not take any parameters");
- execv_git_cmd(args);
- exit(1);
+ exit(interactive_add());
}
git_config(git_add_config);
diff --git a/commit.h b/commit.h
index 467872e..64e1d4b 100644
--- a/commit.h
+++ b/commit.h
@@ -122,4 +122,13 @@ extern struct commit_list *get_shallow_commits(struct object_array *heads,
int depth, int shallow_flag, int not_shallow_flag);
int in_merge_bases(struct commit *, struct commit **, int);
+
+extern const unsigned char *
+create_commit(const unsigned char *tree_sha1,
+ unsigned char parent_sha1[][20], int parents,
+ const char *author_info, const char *committer_info,
+ const char *message, int length);
+
+extern int interactive_add(void);
+
#endif /* COMMIT_H */
--
1.5.2.GIT
^ permalink raw reply related
* [PATCH 5/9] Introduce strbuf_read_fd().
From: Kristian Høgsberg @ 2007-09-06 0:23 UTC (permalink / raw)
To: git; +Cc: Kristian Høgsberg
In-Reply-To: <11890382253220-git-send-email-krh@redhat.com>
This function reads from a given fd into a strbuf until end of file.
Signed-off-by: Kristian Høgsberg <krh@redhat.com>
---
strbuf.c | 19 +++++++++++++++++++
strbuf.h | 1 +
2 files changed, 20 insertions(+), 0 deletions(-)
diff --git a/strbuf.c b/strbuf.c
index 2805c11..fcfc05e 100644
--- a/strbuf.c
+++ b/strbuf.c
@@ -50,6 +50,25 @@ void read_line(struct strbuf *sb, FILE *fp, int term) {
strbuf_end(sb);
}
+int strbuf_read_fd(struct strbuf *sb, int fd)
+{
+ int len, total = 0;
+
+ do {
+ strbuf_grow(sb, 1024);
+ len = xread(fd, sb->buf + sb->len, sb->alloc - sb->len);
+ if (len > 0) {
+ total += len;
+ sb->len += len;
+ }
+ } while (len > 0);
+
+ if (len < 0)
+ return len;
+
+ return total;
+}
+
void strbuf_printf(struct strbuf *sb, const char *fmt, ...)
{
char buffer[2048];
diff --git a/strbuf.h b/strbuf.h
index 1e5d09e..6e630ea 100644
--- a/strbuf.h
+++ b/strbuf.h
@@ -12,5 +12,6 @@ extern void read_line(struct strbuf *, FILE *, int);
extern void strbuf_add(struct strbuf *sb, const char *data, size_t len);
extern void strbuf_add_char(struct strbuf *sb, int ch);
extern void strbuf_printf(struct strbuf *sb, const char *fmt, ...);
+extern int strbuf_read_fd(struct strbuf *sb, int fd);
#endif /* STRBUF_H */
--
1.5.2.GIT
^ permalink raw reply related
* [PATCH 3/9] Add strbuf_printf() to do formatted printing to a strbuf.
From: Kristian Høgsberg @ 2007-09-06 0:23 UTC (permalink / raw)
To: git; +Cc: Kristian Høgsberg
In-Reply-To: <11890382242333-git-send-email-krh@redhat.com>
Also, expose strbuf_add() and strbuf_add_char() to add raw data to the buffer.
Signed-off-by: Kristian Høgsberg <krh@redhat.com>
---
strbuf.c | 69 +++++++++++++++++++++++++++++++++++++++++++++++++++++++------
strbuf.h | 3 ++
2 files changed, 65 insertions(+), 7 deletions(-)
diff --git a/strbuf.c b/strbuf.c
index e33d06b..2805c11 100644
--- a/strbuf.c
+++ b/strbuf.c
@@ -11,16 +11,26 @@ static void strbuf_begin(struct strbuf *sb) {
strbuf_init(sb);
}
-static void inline strbuf_add(struct strbuf *sb, int ch) {
- if (sb->alloc <= sb->len) {
- sb->alloc = sb->alloc * 3 / 2 + 16;
- sb->buf = xrealloc(sb->buf, sb->alloc);
- }
+static void inline strbuf_grow(struct strbuf *sb, size_t extra)
+{
+ ALLOC_GROW(sb->buf, sb->len + extra, sb->alloc);
+}
+
+void strbuf_add(struct strbuf *sb, const char *data, size_t len)
+{
+ strbuf_grow(sb, len);
+ memcpy(sb->buf + sb->len, data, len);
+ sb->len += len;
+}
+
+void strbuf_add_char(struct strbuf *sb, int ch)
+{
+ strbuf_grow(sb, 1);
sb->buf[sb->len++] = ch;
}
static void strbuf_end(struct strbuf *sb) {
- strbuf_add(sb, 0);
+ strbuf_add_char(sb, 0);
}
void read_line(struct strbuf *sb, FILE *fp, int term) {
@@ -33,9 +43,54 @@ void read_line(struct strbuf *sb, FILE *fp, int term) {
while ((ch = fgetc(fp)) != EOF) {
if (ch == term)
break;
- strbuf_add(sb, ch);
+ strbuf_add_char(sb, ch);
}
if (ch == EOF && sb->len == 0)
sb->eof = 1;
strbuf_end(sb);
}
+
+void strbuf_printf(struct strbuf *sb, const char *fmt, ...)
+{
+ char buffer[2048];
+ va_list args;
+ int len, size = 2 * sizeof buffer;
+
+ va_start(args, fmt);
+ len = vsnprintf(buffer, sizeof(buffer), fmt, args);
+ va_end(args);
+
+ if (len > sizeof(buffer)) {
+ /*
+ * Didn't fit in the buffer, but this vsnprintf at
+ * least gives us the required length back. Grow the
+ * buffer acccordingly and try again.
+ */
+ strbuf_grow(sb, len);
+ va_start(args, fmt);
+ len = vsnprintf(sb->buf + sb->len,
+ sb->alloc - sb->len, fmt, args);
+ va_end(args);
+ } else if (len >= 0) {
+ /*
+ * The initial vsnprintf fit in the temp buffer, just
+ * copy it to the strbuf.
+ */
+ strbuf_add(sb, buffer, len);
+ } else {
+ /*
+ * This vnsprintf sucks and just returns -1 when the
+ * buffer is too small. Keep doubling the size until
+ * it fits.
+ */
+ while (len < 0) {
+ strbuf_grow(sb, size);
+ va_start(args, fmt);
+ len = vsnprintf(sb->buf + sb->len,
+ sb->alloc - sb->len, fmt, args);
+ va_end(args);
+ size *= 2;
+ }
+ }
+}
+
diff --git a/strbuf.h b/strbuf.h
index 74cc012..1e5d09e 100644
--- a/strbuf.h
+++ b/strbuf.h
@@ -9,5 +9,8 @@ struct strbuf {
extern void strbuf_init(struct strbuf *);
extern void read_line(struct strbuf *, FILE *, int);
+extern void strbuf_add(struct strbuf *sb, const char *data, size_t len);
+extern void strbuf_add_char(struct strbuf *sb, int ch);
+extern void strbuf_printf(struct strbuf *sb, const char *fmt, ...);
#endif /* STRBUF_H */
--
1.5.2.GIT
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox