* Re: [PATCH 2/2] index-pack: reduce memory usage when the pack has large blobs
From: Nguyen Thai Ngoc Duy @ 2012-02-25 1:49 UTC (permalink / raw)
To: Ian Kumlien; +Cc: git
In-Reply-To: <20120224161613.GH9526@pomac.netswarm.net>
2012/2/24 Ian Kumlien <pomac@vapor.com>:
> Writing objects: 100% (1425/1425), 56.06 MiB | 4.62 MiB/s, done.
> Total 1425 (delta 790), reused 1425 (delta 790)
> fatal: Out of memory, malloc failed (tried to allocate 3310214315 bytes)
> fatal: Out of memory, malloc failed (tried to allocate 3310214315 bytes)
> fatal: Out of memory, malloc failed (tried to allocate 3310214315 bytes)
> fatal: Out of memory, malloc failed (tried to allocate 3310214315 bytes)
> To ../test_data/
> ! [remote rejected] master -> master (missing necessary objects)
> ! [remote rejected] origin/HEAD -> origin/HEAD (missing necessary objects)
> ! [remote rejected] origin/master -> origin/master (missing necessary objects)
> error: failed to push some refs to '../test_data/'
>
> So there are additional code paths to look at... =(
I can't say where that came from. Does this help? (Space damaged, may
need manual application)
-- 8< --
diff --git a/builtin/rev-list.c b/builtin/rev-list.c
index 264e3ae..6dc46eb 100644
--- a/builtin/rev-list.c
+++ b/builtin/rev-list.c
@@ -183,7 +183,8 @@ static void show_object(struct object *obj,
struct rev_list_info *info = cb_data;
finish_object(obj, path, component, cb_data);
- if (info->revs->verify_objects && !obj->parsed && obj->type !=
OBJ_COMMIT)
+ if (info->revs->verify_objects && !obj->parsed &&
+ obj->type != OBJ_COMMIT && obj->type != OBJ_BLOB)
parse_object(obj->sha1);
show_object_with_name(stdout, obj, path, component);
}
-- 8< --
If not, you might need to apply this to generate coredump, then look
and see where that failed malloc comes from
-- 8< --
diff --git a/wrapper.c b/wrapper.c
index 85f09df..03f423e 100644
--- a/wrapper.c
+++ b/wrapper.c
@@ -40,9 +40,11 @@ void *xmalloc(size_t size)
ret = malloc(size);
if (!ret && !size)
ret = malloc(1);
- if (!ret)
+ if (!ret) {
+ *(char*)0 = 1;
die("Out of memory, malloc failed (tried to
allocate %lu bytes)",
(unsigned long)size);
+ }
}
#ifdef XMALLOC_POISON
memset(ret, 0xA5, size);
-- 8< --
--
Duy
^ permalink raw reply related
* Re: git-subtree Ready #2
From: David A. Greene @ 2012-02-25 5:00 UTC (permalink / raw)
To: Avery Pennarun; +Cc: Junio C Hamano, Jeff King, git
In-Reply-To: <CAHqTa-1fbi5W7R2fLu3bp7Yuv_ZB9nxhgjHkLGuU8-V4016+JA@mail.gmail.com>
Avery Pennarun <apenwarr@gmail.com> writes:
> On Fri, Feb 24, 2012 at 3:56 PM, Junio C Hamano <gitster@pobox.com> wrote:
>> Avery Pennarun <apenwarr@gmail.com> writes:
>>> Overall I agree that there's little benefit in preserving the history,
>>> at least as far as I can see, *except* that some code changes were
>>> submitted by people other than me and squashing those changes might
>>> conceivably cause licensing confusion down the road.
>>
>> That is a good point, and it sounds like a good enough justification to
>> merge with history, at least for me.
>
> Should we filter-branch or rebase the history first, or just leave it as is?
>
> Like I said, since I don't expect there to be any more back-and-forth
> development, rebasing should be pretty harmless.
Catching up on e-mail. :)
I'm happy to do either (rebase or filter-branch). Just let me know.
I'm about the send the test-lib.sh patch separately as it's a prereq for
putting git-subtrees tests in contrib and I think it's generally useful
anyway.
-Dave
^ permalink raw reply
* send-email SMTP/TLS Debugging
From: David A. Greene @ 2012-02-25 5:54 UTC (permalink / raw)
To: git
Is there some way to turn on TLS authentication debugging using
git-send-mail? I'm trying to send a patch but git (or the mail server,
I suppose) keeps telling me I have "Incorrect authentication data."
I've checked the settings in .git/config multiple times and they look
correct. How can I debug this further?
Thanks!
-Dave
^ permalink raw reply
* [PATCH] pack-objects: Fix compilation with NO_PTHREDS
From: Michał Kiedrowicz @ 2012-02-25 8:16 UTC (permalink / raw)
To: git; +Cc: Nguyễn Thái Ngọc Duy, Michał Kiedrowicz
It looks like commit 99fb6e04 (pack-objects: convert to use
parse_options(), 2012-02-01) moved the #ifdef NO_PTHREDS around but
hasn't noticed that the 'arg' variable no longer is available.
Signed-off-by: Michał Kiedrowicz <michal.kiedrowicz@gmail.com>
---
builtin/pack-objects.c | 2 +-
1 files changed, 1 insertions(+), 1 deletions(-)
diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c
index db09cf7..71af246 100644
--- a/builtin/pack-objects.c
+++ b/builtin/pack-objects.c
@@ -2449,7 +2449,7 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
die("bad pack compression level %d", pack_compression_level);
#ifdef NO_PTHREADS
if (delta_search_threads != 1)
- warning("no threads support, ignoring %s", arg);
+ warning("no threads support, ignoring --threads");
#endif
if (!pack_to_stdout && !pack_size_limit)
pack_size_limit = pack_size_limit_cfg;
--
1.7.8.4
^ permalink raw reply related
* Re: [PATCH 2/8] gitweb: Use print_diff_chunk() for both side-by-side and inline diffs
From: Michał Kiedrowicz @ 2012-02-25 9:00 UTC (permalink / raw)
To: Jakub Narebski; +Cc: git
In-Reply-To: <m3k43ttlh9.fsf@localhost.localdomain>
Jakub Narebski <jnareb@gmail.com> wrote:
> BTW. I didn't examine the final code, but what happens for binary
> diffs that git supports? Is it handled outside print_diff_chunk()?
gitweb doesn't produce binary diffs at all except for "?a=patch", but
it isn't HTML'ed. So I don't think there anything I can do with it.
^ permalink raw reply
* Re: git-subtree Ready #2
From: Junio C Hamano @ 2012-02-25 9:00 UTC (permalink / raw)
To: David A. Greene; +Cc: Avery Pennarun, Jeff King, git
In-Reply-To: <87hayfv75y.fsf@smith.obbligato.org>
greened@obbligato.org (David A. Greene) writes:
> Avery Pennarun <apenwarr@gmail.com> writes:
>
>> Should we filter-branch or rebase the history first, or just leave it as is?
>>
>> Like I said, since I don't expect there to be any more back-and-forth
>> development, rebasing should be pretty harmless.
>
> Catching up on e-mail. :)
>
> I'm happy to do either (rebase or filter-branch). Just let me know.
I would understand Avery's "should we filter-branch/rebase, or is it OK
as-is?", but I do not understand what you mean by "either rebase or
filter-branch is fine".
> I'm about the send the test-lib.sh patch separately...
Thanks, this I understand and should not be a part of git-subtree topic.
^ permalink raw reply
* Re: [PATCH] pack-objects: Fix compilation with NO_PTHREDS
From: Junio C Hamano @ 2012-02-25 9:02 UTC (permalink / raw)
To: Michał Kiedrowicz; +Cc: git, Nguyễn Thái Ngọc Duy
In-Reply-To: <1330157769-7884-1-git-send-email-michal.kiedrowicz@gmail.com>
Michał Kiedrowicz <michal.kiedrowicz@gmail.com> writes:
> It looks like commit 99fb6e04 (pack-objects: convert to use
> parse_options(), 2012-02-01) moved the #ifdef NO_PTHREDS around but
> hasn't noticed that the 'arg' variable no longer is available.
>
> Signed-off-by: Michał Kiedrowicz <michal.kiedrowicz@gmail.com>
Thanks. Nguyễn, a quick double-check and an Ack?
> ---
> builtin/pack-objects.c | 2 +-
> 1 files changed, 1 insertions(+), 1 deletions(-)
>
> diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c
> index db09cf7..71af246 100644
> --- a/builtin/pack-objects.c
> +++ b/builtin/pack-objects.c
> @@ -2449,7 +2449,7 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
> die("bad pack compression level %d", pack_compression_level);
> #ifdef NO_PTHREADS
> if (delta_search_threads != 1)
> - warning("no threads support, ignoring %s", arg);
> + warning("no threads support, ignoring --threads");
> #endif
> if (!pack_to_stdout && !pack_size_limit)
> pack_size_limit = pack_size_limit_cfg;
^ permalink raw reply
* [PATCH] grep -P: Fix matching ^ and $
From: Michał Kiedrowicz @ 2012-02-25 9:24 UTC (permalink / raw)
To: git; +Cc: Zbigniew Jędrzejewski-Szmek, Michał Kiedrowicz
When `git-grep` is run with -P/--perl-regexp, it doesn't match ^ and $ at
the beginning/end of the line. This is because PCRE normally matches ^
and $ at the beginning/end of the whole text, not for each line, and git-grep
firstly passes a large chunk of text (possibly containing many lines) to
pcre_exec() before it splits the text into lines. This makes `git-grep -P`
behave differently from `git-grep -E` and also from `grep -P` and `pcregrep`:
$ cat file
a
b
$ git --no-pager grep --no-index -P '^ ' file
$ git --no-pager grep --no-index -E '^ ' file
file: b
$ grep -c -P '^ ' file
b
$ pcregrep -c '^ ' file
b
Reported-by: Zbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
Signed-off-by: Michał Kiedrowicz <michal.kiedrowicz@gmail.com>
---
grep.c | 2 +-
t/t7810-grep.sh | 23 +++++++++++++++++++++++
2 files changed, 24 insertions(+), 1 deletions(-)
diff --git a/grep.c b/grep.c
index 3821400..f492d26 100644
--- a/grep.c
+++ b/grep.c
@@ -79,7 +79,7 @@ static void compile_pcre_regexp(struct grep_pat *p, const struct grep_opt *opt)
{
const char *error;
int erroffset;
- int options = 0;
+ int options = PCRE_MULTILINE;
if (opt->ignore_case)
options |= PCRE_CASELESS;
diff --git a/t/t7810-grep.sh b/t/t7810-grep.sh
index 75f4716..dd6e6d5 100755
--- a/t/t7810-grep.sh
+++ b/t/t7810-grep.sh
@@ -47,6 +47,13 @@ test_expect_success setup '
echo vvv >t/v &&
mkdir t/a &&
echo vvv >t/a/v &&
+ {
+ echo "line without leading space1"
+ echo " line with leading space1"
+ echo " line with leading space2"
+ echo " line with leading space3"
+ echo "line without leading space2"
+ } >space &&
git add . &&
test_tick &&
git commit -m initial
@@ -893,4 +900,20 @@ test_expect_success 'mimic ack-grep --group' '
test_cmp expected actual
'
+cat >expected <<EOF
+space: line with leading space1
+space: line with leading space2
+space: line with leading space3
+EOF
+
+test_expect_success 'grep -E "^ "' '
+ git grep -E "^ " space >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success "grep -P '^ '" '
+ git grep -P "^ " space >actual &&
+ test_cmp expected actual
+'
+
test_done
--
1.7.8.4
^ permalink raw reply related
* Re: [PATCH] pack-objects: Fix compilation with NO_PTHREDS
From: Nguyen Thai Ngoc Duy @ 2012-02-25 9:24 UTC (permalink / raw)
To: Junio C Hamano; +Cc: Michał Kiedrowicz, git
In-Reply-To: <7vty2fffpp.fsf@alter.siamese.dyndns.org>
2012/2/25 Junio C Hamano <gitster@pobox.com>:
> Michał Kiedrowicz <michal.kiedrowicz@gmail.com> writes:
>
>> It looks like commit 99fb6e04 (pack-objects: convert to use
>> parse_options(), 2012-02-01) moved the #ifdef NO_PTHREDS around but
>> hasn't noticed that the 'arg' variable no longer is available.
>>
>> Signed-off-by: Michał Kiedrowicz <michal.kiedrowicz@gmail.com>
>
> Thanks. Nguyễn, a quick double-check and an Ack?
Tested and acked.
--
Duy
^ permalink raw reply
* Re: Improving merge messages for 1.7.10 and making "pull" easier
From: Ævar Arnfjörð Bjarmason @ 2012-02-25 9:27 UTC (permalink / raw)
To: Junio C Hamano; +Cc: Git Mailing List, Thomas Rast, Linus Torvalds
In-Reply-To: <7vy5rsyq9w.fsf@alter.siamese.dyndns.org>
On Fri, Feb 24, 2012 at 20:39, Junio C Hamano <gitster@pobox.com> wrote:
> Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:
>
>> Firstly (and as a more general thing) I think we should add a mention
>> of "git merge --abort" to the message, just saving an empty file is
>> not sufficient to fully clear the merge state:
>
> Makes sense, but the new message does not quite parse.
>
>>> "Lines starting with '#' will be ignored, and an empty message followed\n"
>>> "by 'git merge --abort' the merge.\n");
>
> Perhaps s/the merge/aborts &/ or something.
Yeah, it needs better wording.
>> Additionally, perhaps it would be a good idea to:
>>
>> * Detect if the user didn't run this explicitly but implicitly from a
>> "git pull". We could pass some env var along or another option
>> (e.g. --internal-from-porcelain=pull) and add this:
>>
>> You've merged implicitly via a "git pull", if you're just
>> updating some local work in progress to keep up with upstream
>> you may want to use "git pull --rebase" instead (or set the
>> pull.rebase configuration variable) to rebase instead of merge.
>
> Won't this message be given to _all_ users of "git pull", even to the ones
> who already have decided correctly that "pull" is the right thing in their
> situation? With a new advice.* settings to squelch it, perhaps.
Yeah, I'm not sure it's a good idea to add it given that.
>> * Explicitly check if we're merging an updated upstream into the
>> work-in-progress topic,...
>
> It might be a worthy goal, but how would we detect it? A few examples
> that we shouldn't give an unhelpful advice with a false positive are
> merges into:
>
> - The 'master' branch used by people who use Git as an improved CVS, when
> they do an equivalent of 'cvs update'. Merging the updated 'master'
> from the central repository into their 'master' that contains their
> work that may or may not be ready to be pushed back is how their
> project works. It is a norm for them to make such a merge, even though
> more experienced people may prefer to see the history of their project
> kept cleaner by suggesting their project participants to use their own
> topic branches.
>
> - Integration branches like my 'next', when it gets a merge from
> 'master'. This is "merging an updated upstream" but is done in order to
> keep the promise that 'next' would contain everything in 'master'.
>
> And what alternative would we offer? If we were to suggest "rebase", we
> would also need to consider the topic of the other a-couple-of-days-old
> thread to detect which part of history is no longer subject to rewrite.
>
>> I work with a lot of inexperienced git users and a lot of them are
>> going to be very confused by this change. I still think it's a good
>> change to make, but we could do a lot more to mitigate the inevitable
>> confusion.
>
> What exact change are you talking about with "this change"? Earlier you
> had a chance to edit the merge log only when it needed your help resolving
> (hence you did a separate "git commit" to record it) but you had to "git
> commit --amend" (or start with "git merge --no-commit") to edit the merge
> log if it did not need any help resolving conflicts, but now you do not
> have to. Is that the change you have in mind?
Yes, or more explicitly if you do:
git commit
<your upstream moves forward>
git pull
You'll now be presented with an editor asking you to enter a merge
message, whereas before it just silently created a merge commit.
> I would like to know how that would lead to an "inevitable confusion".
> Admittedly, the original without any "# Please do X" comment, the user may
> wonder what is being asked of him when he sees the editor for the first
> time, but I thought Thomas's patch took care of that issue.
I mean that people who use this babytalk subset of git and have been
doing so for months/years without having editor dialogs pop up every
time they pull are going to be confused when that suddenly starts
happening.
>> One thing that would help these users in particular would be to have
>> some easy to use replacement for their frequent use of "git
>> pull".
>
> After this part, I think you shifted into a different topic.
>
> I have mixed feelings about "rebase your unpublished work and keep it
> always a descendant of the upstream" workflow you seem to be advocating.
> It _might_ deserve a bit more visibility, but I do not think rewording
> this message done during "merge" is the place to do so.
Yeah, agreed.
>> They don't often commit their work (because of git inexperience) so
>> rebasing will error out because the tree is unclean.
>
> That is a *good* thing, isn't it? There lies the perfect opportunity for
> them to train their fingers to commit first and then rebase.
That's true, anyway I'll submit an improved patch for adding --abort
to the current message. I think the other suggestions either aren't
doable or weren't appropriate.
^ permalink raw reply
* Re: [PATCH] grep -P: Fix matching ^ and $
From: Michał Kiedrowicz @ 2012-02-25 9:30 UTC (permalink / raw)
To: git; +Cc: Michał Kiedrowicz, Zbigniew Jędrzejewski-Szmek
In-Reply-To: <1330161868-7954-1-git-send-email-michal.kiedrowicz@gmail.com>
Michał Kiedrowicz <michal.kiedrowicz@gmail.com> wrote:
> When `git-grep` is run with -P/--perl-regexp, it doesn't match ^ and $ at
> the beginning/end of the line. This is because PCRE normally matches ^
> and $ at the beginning/end of the whole text, not for each line, and git-grep
> firstly passes a large chunk of text (possibly containing many lines) to
> pcre_exec() before it splits the text into lines. This makes `git-grep -P`
> behave differently from `git-grep -E` and also from `grep -P` and `pcregrep`:
>
> $ cat file
> a
> b
> $ git --no-pager grep --no-index -P '^ ' file
> $ git --no-pager grep --no-index -E '^ ' file
> file: b
> $ grep -c -P '^ ' file
> b
> $ pcregrep -c '^ ' file
> b
>
Original report:
http://permalink.gmane.org/gmane.comp.version-control.git/190830
> Reported-by: Zbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
> Signed-off-by: Michał Kiedrowicz <michal.kiedrowicz@gmail.com>
> ---
> grep.c | 2 +-
> t/t7810-grep.sh | 23 +++++++++++++++++++++++
> 2 files changed, 24 insertions(+), 1 deletions(-)
>
> diff --git a/grep.c b/grep.c
> index 3821400..f492d26 100644
> --- a/grep.c
> +++ b/grep.c
> @@ -79,7 +79,7 @@ static void compile_pcre_regexp(struct grep_pat *p, const struct grep_opt *opt)
> {
> const char *error;
> int erroffset;
> - int options = 0;
> + int options = PCRE_MULTILINE;
>
> if (opt->ignore_case)
> options |= PCRE_CASELESS;
> diff --git a/t/t7810-grep.sh b/t/t7810-grep.sh
> index 75f4716..dd6e6d5 100755
> --- a/t/t7810-grep.sh
> +++ b/t/t7810-grep.sh
> @@ -47,6 +47,13 @@ test_expect_success setup '
> echo vvv >t/v &&
> mkdir t/a &&
> echo vvv >t/a/v &&
> + {
> + echo "line without leading space1"
> + echo " line with leading space1"
> + echo " line with leading space2"
> + echo " line with leading space3"
> + echo "line without leading space2"
> + } >space &&
> git add . &&
> test_tick &&
> git commit -m initial
> @@ -893,4 +900,20 @@ test_expect_success 'mimic ack-grep --group' '
> test_cmp expected actual
> '
>
> +cat >expected <<EOF
> +space: line with leading space1
> +space: line with leading space2
> +space: line with leading space3
> +EOF
> +
> +test_expect_success 'grep -E "^ "' '
> + git grep -E "^ " space >actual &&
> + test_cmp expected actual
> +'
> +
> +test_expect_success "grep -P '^ '" '
> + git grep -P "^ " space >actual &&
> + test_cmp expected actual
> +'
> +
> test_done
^ permalink raw reply
* [PATCH 0/4] index-pack improvements
From: Nguyễn Thái Ngọc Duy @ 2012-02-25 10:56 UTC (permalink / raw)
To: git; +Cc: Nguyễn Thái Ngọc Duy
The first two patches are posted already, which help keep memory usage
down in the present of large blobs. The forth patch attempts to
resolve deltas in parallel.
$ time ./git index-pack --threads=1 --verify -v XXXX
Indexing objects: 100% (165375/165375), done.
Resolving deltas: 100% (124749/124749), done.
real 1m15.470s
user 1m14.899s
sys 0m0.552s
$ time ./git index-pack --threads=2 --verify -v XXXX
Indexing objects: 100% (165375/165375), done.
Resolving deltas: 100% (124749/124749), done.
real 0m41.339s
user 1m15.116s
sys 0m0.680s
$ time ./git index-pack --threads=3 --verify -v XXXX
Indexing objects: 100% (165375/165375), done.
Resolving deltas: 100% (124749/124749), done.
real 0m37.008s
user 1m35.742s
sys 0m0.803s
$ time ./git index-pack --verify -v XXXX # four core machine
Indexing objects: 100% (165375/165375), done.
Resolving deltas: 100% (124749/124749), done.
real 0m33.701s
user 1m51.316s
sys 0m0.768s
$ time ./git index-pack --threads=8 --verify -v XXXX
Indexing objects: 100% (165375/165375), done.
Resolving deltas: 100% (124749/124749), done.
real 0m33.638s
user 1m51.783s
sys 0m0.773s
So there's improvement from user persepective, but overhead is too
high (user time from 1m14 to 1m51). Making threaded_second_pass to
process 512 consecutive objects each iteration to reduce contention on
work_mutex does not help. Any ideas?
Nguyễn Thái Ngọc Duy (4):
index-pack --verify: skip sha-1 collision test
index-pack: reduce memory usage when the pack has large blobs
index-pack: move second pass code into separate function
index-pack: support multithreaded delta resolving
Documentation/config.txt | 4 +
Documentation/git-index-pack.txt | 10 ++
Makefile | 2 +-
builtin/index-pack.c | 298 +++++++++++++++++++++++++++++++-------
4 files changed, 260 insertions(+), 54 deletions(-)
--
1.7.8.36.g69ee2
^ permalink raw reply
* [PATCH 1/4] index-pack --verify: skip sha-1 collision test
From: Nguyễn Thái Ngọc Duy @ 2012-02-25 10:56 UTC (permalink / raw)
To: git; +Cc: Nguyễn Thái Ngọc Duy
In-Reply-To: <1330167376-24859-1-git-send-email-pclouds@gmail.com>
index-pack --verify (or verify-pack) is about verifying the pack
itself. SHA-1 collision test is about outside (probably malicious)
objects with the same SHA-1 entering current repo.
SHA-1 collision test is currently done unconditionally. Which means if
you verify an in-repo pack, all objects from the pack will be checked
against objects in repo, which are themselves.
Skip this test for --verify, unless --strict is also specified.
linux-2.6 $ ls -sh .git/objects/pack/pack-e7732c98a8d54840add294c3c562840f78764196.pack
401M .git/objects/pack/pack-e7732c98a8d54840add294c3c562840f78764196.pack
Without the patch (and with another patch to cut out second pass in
index-pack):
linux-2.6 $ time ~/w/git/old index-pack -v --verify .git/objects/pack/pack-e7732c98a8d54840add294c3c562840f78764196.pack
Indexing objects: 100% (1944656/1944656), done.
fatal: pack has 1617280 unresolved deltas
real 1m1.223s
user 0m55.028s
sys 0m0.828s
With the patch:
linux-2.6 $ time ~/w/git/git index-pack -v --verify .git/objects/pack/pack-e7732c98a8d54840add294c3c562840f78764196.pack
Indexing objects: 100% (1944656/1944656), done.
fatal: pack has 1617280 unresolved deltas
real 0m41.714s
user 0m40.994s
sys 0m0.550s
Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
builtin/index-pack.c | 5 +++--
1 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/builtin/index-pack.c b/builtin/index-pack.c
index dd1c5c9..cee83b9 100644
--- a/builtin/index-pack.c
+++ b/builtin/index-pack.c
@@ -62,6 +62,7 @@ static int nr_resolved_deltas;
static int from_stdin;
static int strict;
+static int verify;
static int verbose;
static struct progress *progress;
@@ -461,7 +462,7 @@ static void sha1_object(const void *data, unsigned long size,
enum object_type type, unsigned char *sha1)
{
hash_sha1_file(data, size, typename(type), sha1);
- if (has_sha1_file(sha1)) {
+ if ((strict || !verify) && has_sha1_file(sha1)) {
void *has_data;
enum object_type has_type;
unsigned long has_size;
@@ -1078,7 +1079,7 @@ static void show_pack_info(int stat_only)
int cmd_index_pack(int argc, const char **argv, const char *prefix)
{
- int i, fix_thin_pack = 0, verify = 0, stat_only = 0, stat = 0;
+ int i, fix_thin_pack = 0, stat_only = 0, stat = 0;
const char *curr_pack, *curr_index;
const char *index_name = NULL, *pack_name = NULL;
const char *keep_name = NULL, *keep_msg = NULL;
--
1.7.8.36.g69ee2
^ permalink raw reply related
* [PATCH 3/4] index-pack: move second pass code into separate function
From: Nguyễn Thái Ngọc Duy @ 2012-02-25 10:56 UTC (permalink / raw)
To: git; +Cc: Nguyễn Thái Ngọc Duy
In-Reply-To: <1330167376-24859-1-git-send-email-pclouds@gmail.com>
Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
builtin/index-pack.c | 54 +++++++++++++++++++++++++++----------------------
1 files changed, 30 insertions(+), 24 deletions(-)
diff --git a/builtin/index-pack.c b/builtin/index-pack.c
index ab24dd8..e1e858a 100644
--- a/builtin/index-pack.c
+++ b/builtin/index-pack.c
@@ -719,6 +719,35 @@ static int compare_delta_entry(const void *a, const void *b)
objects[delta_b->obj_no].type);
}
+/*
+ * Second pass:
+ * - for all non-delta objects, look if it is used as a base for
+ * deltas;
+ * - if used as a base, uncompress the object and apply all deltas,
+ * recursively checking if the resulting object is used as a base
+ * for some more deltas.
+ * - if the same object exists in repository and we're not in strict
+ * mode, we skipped the sha-1 collision test in the first pass.
+ * Do it now.
+ */
+static void second_pass(struct object_entry *obj)
+{
+ struct base_data *base_obj = alloc_base_data();
+
+ if (((!strict && !verify) ||
+ (strict && !verify && obj->type == OBJ_BLOB)) &&
+ has_sha1_file(obj->idx.sha1)) {
+ void *data = get_data_from_pack(obj);
+ sha1_object(data, obj->size, obj->type, obj->idx.sha1);
+ free(data);
+ }
+
+ base_obj->obj = obj;
+ base_obj->data = NULL;
+ find_unresolved_deltas(base_obj);
+ display_progress(progress, nr_resolved_deltas);
+}
+
/* Parse all objects and return the pack content SHA1 hash */
static void parse_pack_objects(unsigned char *sha1)
{
@@ -773,38 +802,15 @@ static void parse_pack_objects(unsigned char *sha1)
qsort(deltas, nr_deltas, sizeof(struct delta_entry),
compare_delta_entry);
- /*
- * Second pass:
- * - for all non-delta objects, look if it is used as a base for
- * deltas;
- * - if used as a base, uncompress the object and apply all deltas,
- * recursively checking if the resulting object is used as a base
- * for some more deltas.
- * - if the same object exists in repository and we're not in strict
- * mode, we skipped the sha-1 collision test in the first pass.
- * Do it now.
- */
if (verbose)
progress = start_progress("Resolving deltas", nr_deltas);
for (i = 0; i < nr_objects; i++) {
struct object_entry *obj = &objects[i];
- struct base_data *base_obj = alloc_base_data();
if (is_delta_type(obj->type))
continue;
- if (((!strict && !verify) ||
- (strict && !verify && obj->type == OBJ_BLOB)) &&
- has_sha1_file(obj->idx.sha1)) {
- void *data = get_data_from_pack(obj);
- sha1_object(data, obj->size, obj->type, obj->idx.sha1);
- free(data);
- }
-
- base_obj->obj = obj;
- base_obj->data = NULL;
- find_unresolved_deltas(base_obj);
- display_progress(progress, nr_resolved_deltas);
+ second_pass(obj);
}
}
--
1.7.8.36.g69ee2
^ permalink raw reply related
* [PATCH 2/4] index-pack: reduce memory usage when the pack has large blobs
From: Nguyễn Thái Ngọc Duy @ 2012-02-25 10:56 UTC (permalink / raw)
To: git; +Cc: Nguyễn Thái Ngọc Duy
In-Reply-To: <1330167376-24859-1-git-send-email-pclouds@gmail.com>
This command unpacks every non-delta objects in order to:
1. calculate sha-1
2. do byte-to-byte sha-1 collision test if we happen to have objects
with the same sha-1
3. validate object content in strict mode
All this requires the entire object to stay in memory, a bad news for
giant blobs. This patch lowers memory consumption by not saving the
object in memory whenever possible, calculating SHA-1 while unpacking
the object.
This patch assumes that the collision test is rarely needed. The
collision test will be done later in second pass if necessary, which
puts the entire object back to memory again (We could even do the
collision test without putting the entire object back in memory, by
comparing as we unpack it).
In strict mode, it always keeps non-blob objects in memory for
validation (blobs do not need data validation). "--strict --verify"
also keeps blobs in memory.
Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
builtin/index-pack.c | 74 +++++++++++++++++++++++++++++++++++++++++---------
1 files changed, 61 insertions(+), 13 deletions(-)
diff --git a/builtin/index-pack.c b/builtin/index-pack.c
index cee83b9..ab24dd8 100644
--- a/builtin/index-pack.c
+++ b/builtin/index-pack.c
@@ -277,30 +277,60 @@ static void unlink_base_data(struct base_data *c)
free_base_data(c);
}
-static void *unpack_entry_data(unsigned long offset, unsigned long size)
+static void *unpack_entry_data(unsigned long offset, unsigned long size,
+ enum object_type type, unsigned char *sha1)
{
+ static char fixed_buf[8192];
int status;
git_zstream stream;
- void *buf = xmalloc(size);
+ void *buf;
+ git_SHA_CTX c;
+
+ if (sha1) { /* do hash_sha1_file internally */
+ char hdr[32];
+ int hdrlen = sprintf(hdr, "%s %lu", typename(type), size)+1;
+ git_SHA1_Init(&c);
+ git_SHA1_Update(&c, hdr, hdrlen);
+
+ buf = fixed_buf;
+ } else {
+ buf = xmalloc(size);
+ }
memset(&stream, 0, sizeof(stream));
git_inflate_init(&stream);
stream.next_out = buf;
- stream.avail_out = size;
+ stream.avail_out = buf == fixed_buf ? sizeof(fixed_buf) : size;
do {
stream.next_in = fill(1);
stream.avail_in = input_len;
status = git_inflate(&stream, 0);
use(input_len - stream.avail_in);
+ if (sha1) {
+ git_SHA1_Update(&c, buf, stream.next_out - (unsigned char *)buf);
+ stream.next_out = buf;
+ stream.avail_out = sizeof(fixed_buf);
+ }
} while (status == Z_OK);
if (stream.total_out != size || status != Z_STREAM_END)
bad_object(offset, "inflate returned %d", status);
git_inflate_end(&stream);
+ if (sha1) {
+ git_SHA1_Final(sha1, &c);
+ buf = NULL;
+ }
return buf;
}
-static void *unpack_raw_entry(struct object_entry *obj, union delta_base *delta_base)
+static int is_delta_type(enum object_type type)
+{
+ return (type == OBJ_REF_DELTA || type == OBJ_OFS_DELTA);
+}
+
+static void *unpack_raw_entry(struct object_entry *obj,
+ union delta_base *delta_base,
+ unsigned char *sha1)
{
unsigned char *p;
unsigned long size, c;
@@ -360,7 +390,17 @@ static void *unpack_raw_entry(struct object_entry *obj, union delta_base *delta_
}
obj->hdr_size = consumed_bytes - obj->idx.offset;
- data = unpack_entry_data(obj->idx.offset, obj->size);
+ /*
+ * --verify --strict: sha1_object() does all collision test
+ * --strict: sha1_object() does all except blobs,
+ * blobs tested in second pass
+ * --verify : no collision test
+ * : all in second pass
+ */
+ if (is_delta_type(obj->type) ||
+ (strict && (verify || obj->type != OBJ_BLOB)))
+ sha1 = NULL; /* save unpacked object */
+ data = unpack_entry_data(obj->idx.offset, obj->size, obj->type, sha1);
obj->idx.crc32 = input_crc32;
return data;
}
@@ -461,8 +501,9 @@ static void find_delta_children(const union delta_base *base,
static void sha1_object(const void *data, unsigned long size,
enum object_type type, unsigned char *sha1)
{
- hash_sha1_file(data, size, typename(type), sha1);
- if ((strict || !verify) && has_sha1_file(sha1)) {
+ if (data)
+ hash_sha1_file(data, size, typename(type), sha1);
+ if (data && (strict || !verify) && has_sha1_file(sha1)) {
void *has_data;
enum object_type has_type;
unsigned long has_size;
@@ -511,11 +552,6 @@ static void sha1_object(const void *data, unsigned long size,
}
}
-static int is_delta_type(enum object_type type)
-{
- return (type == OBJ_REF_DELTA || type == OBJ_OFS_DELTA);
-}
-
/*
* This function is part of find_unresolved_deltas(). There are two
* walkers going in the opposite ways.
@@ -702,7 +738,7 @@ static void parse_pack_objects(unsigned char *sha1)
nr_objects);
for (i = 0; i < nr_objects; i++) {
struct object_entry *obj = &objects[i];
- void *data = unpack_raw_entry(obj, &delta->base);
+ void *data = unpack_raw_entry(obj, &delta->base, obj->idx.sha1);
obj->real_type = obj->type;
if (is_delta_type(obj->type)) {
nr_deltas++;
@@ -744,6 +780,9 @@ static void parse_pack_objects(unsigned char *sha1)
* - if used as a base, uncompress the object and apply all deltas,
* recursively checking if the resulting object is used as a base
* for some more deltas.
+ * - if the same object exists in repository and we're not in strict
+ * mode, we skipped the sha-1 collision test in the first pass.
+ * Do it now.
*/
if (verbose)
progress = start_progress("Resolving deltas", nr_deltas);
@@ -753,6 +792,15 @@ static void parse_pack_objects(unsigned char *sha1)
if (is_delta_type(obj->type))
continue;
+
+ if (((!strict && !verify) ||
+ (strict && !verify && obj->type == OBJ_BLOB)) &&
+ has_sha1_file(obj->idx.sha1)) {
+ void *data = get_data_from_pack(obj);
+ sha1_object(data, obj->size, obj->type, obj->idx.sha1);
+ free(data);
+ }
+
base_obj->obj = obj;
base_obj->data = NULL;
find_unresolved_deltas(base_obj);
--
1.7.8.36.g69ee2
^ permalink raw reply related
* [PATCH 4/4] index-pack: support multithreaded delta resolving
From: Nguyễn Thái Ngọc Duy @ 2012-02-25 10:56 UTC (permalink / raw)
To: git; +Cc: Nguyễn Thái Ngọc Duy
In-Reply-To: <1330167376-24859-1-git-send-email-pclouds@gmail.com>
Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
Documentation/config.txt | 4 +
Documentation/git-index-pack.txt | 10 ++
Makefile | 2 +-
builtin/index-pack.c | 197 ++++++++++++++++++++++++++++++++------
4 files changed, 182 insertions(+), 31 deletions(-)
diff --git a/Documentation/config.txt b/Documentation/config.txt
index e55dae1..965304b 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -445,6 +445,10 @@ for all users/operating systems, except on the largest projects.
You probably do not need to adjust this value.
+
Common unit suffixes of 'k', 'm', or 'g' are supported.
++
+When gitlink:git-index-pack[1] runs on more than one thread, this
+value is applied per thread so the total amount of used memory depends
+on how many threads are used.
core.bigFileThreshold::
Files larger than this size are stored deflated, without
diff --git a/Documentation/git-index-pack.txt b/Documentation/git-index-pack.txt
index 909687f..7e5f61b 100644
--- a/Documentation/git-index-pack.txt
+++ b/Documentation/git-index-pack.txt
@@ -74,6 +74,16 @@ OPTIONS
--strict::
Die, if the pack contains broken objects or links.
+--threads=<n>::
+ Specifies the number of threads to spawn when resolving
+ deltas. This requires that index-pack be compiled with
+ pthreads otherwise this option is ignored with a warning.
+ This is meant to reduce packing time on multiprocessor
+ machines. The required amount of memory for the delta search
+ window is however multiplied by the number of threads.
+ Specifying 0 will cause git to auto-detect the number of CPU's
+ and set the number of threads accordingly.
+
Note
----
diff --git a/Makefile b/Makefile
index 1fb1705..5fae875 100644
--- a/Makefile
+++ b/Makefile
@@ -2159,7 +2159,7 @@ builtin/branch.o builtin/checkout.o builtin/clone.o builtin/reset.o branch.o tra
builtin/bundle.o bundle.o transport.o: bundle.h
builtin/bisect--helper.o builtin/rev-list.o bisect.o: bisect.h
builtin/clone.o builtin/fetch-pack.o transport.o: fetch-pack.h
-builtin/grep.o builtin/pack-objects.o transport-helper.o thread-utils.o: thread-utils.h
+builtin/index-pack.o builtin/grep.o builtin/pack-objects.o transport-helper.o thread-utils.o: thread-utils.h
builtin/send-pack.o transport.o: send-pack.h
builtin/log.o builtin/shortlog.o: shortlog.h
builtin/prune.o builtin/reflog.o reachable.o: reachable.h
diff --git a/builtin/index-pack.c b/builtin/index-pack.c
index e1e858a..120195a 100644
--- a/builtin/index-pack.c
+++ b/builtin/index-pack.c
@@ -9,6 +9,7 @@
#include "progress.h"
#include "fsck.h"
#include "exec_cmd.h"
+#include "thread-utils.h"
static const char index_pack_usage[] =
"git index-pack [-v] [-o <index-file>] [--keep | --keep=<msg>] [--verify] [--strict] (<pack-file> | --stdin [--fix-thin] [<pack-file>])";
@@ -38,6 +39,15 @@ struct base_data {
int ofs_first, ofs_last;
};
+struct thread_local {
+#ifndef NO_PTHREADS
+ pthread_t thread;
+#endif
+ struct base_data *base_cache;
+ size_t base_cache_used;
+ int nr_resolved_deltas;
+};
+
/*
* Even if sizeof(union delta_base) == 24 on 64-bit archs, we really want
* to memcmp() only the first 20 bytes.
@@ -54,11 +64,12 @@ struct delta_entry {
static struct object_entry *objects;
static struct delta_entry *deltas;
-static struct base_data *base_cache;
-static size_t base_cache_used;
+static struct thread_local *thread_data;
static int nr_objects;
+static int nr_processed;
static int nr_deltas;
static int nr_resolved_deltas;
+static int nr_threads;
static int from_stdin;
static int strict;
@@ -76,6 +87,42 @@ static git_SHA_CTX input_ctx;
static uint32_t input_crc32;
static int input_fd, output_fd, pack_fd;
+#ifndef NO_PTHREADS
+
+static pthread_mutex_t read_mutex;
+#define read_lock() pthread_mutex_lock(&read_mutex)
+#define read_unlock() pthread_mutex_unlock(&read_mutex)
+
+static pthread_mutex_t work_mutex;
+#define work_lock() pthread_mutex_lock(&work_mutex)
+#define work_unlock() pthread_mutex_unlock(&work_mutex)
+
+/*
+ * Mutex and conditional variable can't be statically-initialized on Windows.
+ */
+static void init_thread(void)
+{
+ init_recursive_mutex(&read_mutex);
+ pthread_mutex_init(&work_mutex, NULL);
+}
+
+static void cleanup_thread(void)
+{
+ pthread_mutex_destroy(&read_mutex);
+ pthread_mutex_destroy(&work_mutex);
+}
+
+#else
+
+#define read_lock()
+#define read_unlock()
+
+#define work_lock()
+#define work_unlock()
+
+#endif
+
+
static int mark_link(struct object *obj, int type, void *data)
{
if (!obj)
@@ -224,6 +271,18 @@ static NORETURN void bad_object(unsigned long offset, const char *format, ...)
die("pack has bad object at offset %lu: %s", offset, buf);
}
+static struct thread_local *get_thread_data()
+{
+#ifndef NO_PTHREADS
+ int i;
+ pthread_t self = pthread_self();
+ for (i = 1; i < nr_threads; i++)
+ if (self == thread_data[i].thread)
+ return &thread_data[i];
+#endif
+ return &thread_data[0];
+}
+
static struct base_data *alloc_base_data(void)
{
struct base_data *base = xmalloc(sizeof(struct base_data));
@@ -238,15 +297,16 @@ static void free_base_data(struct base_data *c)
if (c->data) {
free(c->data);
c->data = NULL;
- base_cache_used -= c->size;
+ get_thread_data()->base_cache_used -= c->size;
}
}
static void prune_base_data(struct base_data *retain)
{
struct base_data *b;
- for (b = base_cache;
- base_cache_used > delta_base_cache_limit && b;
+ struct thread_local *data = get_thread_data();
+ for (b = data->base_cache;
+ data->base_cache_used > delta_base_cache_limit && b;
b = b->child) {
if (b->data && b != retain)
free_base_data(b);
@@ -258,22 +318,23 @@ static void link_base_data(struct base_data *base, struct base_data *c)
if (base)
base->child = c;
else
- base_cache = c;
+ get_thread_data()->base_cache = c;
c->base = base;
c->child = NULL;
if (c->data)
- base_cache_used += c->size;
+ get_thread_data()->base_cache_used += c->size;
prune_base_data(c);
}
static void unlink_base_data(struct base_data *c)
{
- struct base_data *base = c->base;
+ struct base_data *base;
+ base = c->base;
if (base)
base->child = NULL;
else
- base_cache = NULL;
+ get_thread_data()->base_cache = NULL;
free_base_data(c);
}
@@ -503,19 +564,25 @@ static void sha1_object(const void *data, unsigned long size,
{
if (data)
hash_sha1_file(data, size, typename(type), sha1);
- if (data && (strict || !verify) && has_sha1_file(sha1)) {
- void *has_data;
- enum object_type has_type;
- unsigned long has_size;
- has_data = read_sha1_file(sha1, &has_type, &has_size);
- if (!has_data)
- die("cannot read existing object %s", sha1_to_hex(sha1));
- if (size != has_size || type != has_type ||
- memcmp(data, has_data, size) != 0)
- die("SHA1 COLLISION FOUND WITH %s !", sha1_to_hex(sha1));
- free(has_data);
+ if (data && (strict || !verify)) {
+ read_lock();
+ if (has_sha1_file(sha1)) {
+ void *has_data;
+ enum object_type has_type;
+ unsigned long has_size;
+ has_data = read_sha1_file(sha1, &has_type, &has_size);
+ read_unlock();
+ if (!has_data)
+ die("cannot read existing object %s", sha1_to_hex(sha1));
+ if (size != has_size || type != has_type ||
+ memcmp(data, has_data, size) != 0)
+ die("SHA1 COLLISION FOUND WITH %s !", sha1_to_hex(sha1));
+ free(has_data);
+ } else
+ read_unlock();
}
if (strict) {
+ read_lock();
if (type == OBJ_BLOB) {
struct blob *blob = lookup_blob(sha1);
if (blob)
@@ -549,6 +616,7 @@ static void sha1_object(const void *data, unsigned long size,
}
obj->flags |= FLAG_CHECKED;
}
+ read_unlock();
}
}
@@ -589,7 +657,7 @@ static void *get_base_data(struct base_data *c)
if (!delta_nr) {
c->data = get_data_from_pack(obj);
c->size = obj->size;
- base_cache_used += c->size;
+ get_thread_data()->base_cache_used += c->size;
prune_base_data(c);
}
for (; delta_nr > 0; delta_nr--) {
@@ -605,7 +673,7 @@ static void *get_base_data(struct base_data *c)
free(raw);
if (!c->data)
bad_object(obj->idx.offset, "failed to apply delta");
- base_cache_used += c->size;
+ get_thread_data()->base_cache_used += c->size;
prune_base_data(c);
}
free(delta);
@@ -633,7 +701,7 @@ static void resolve_delta(struct object_entry *delta_obj,
bad_object(delta_obj->idx.offset, "failed to apply delta");
sha1_object(result->data, result->size, delta_obj->real_type,
delta_obj->idx.sha1);
- nr_resolved_deltas++;
+ get_thread_data()->nr_resolved_deltas++;
}
static struct base_data *find_unresolved_deltas_1(struct base_data *base,
@@ -745,7 +813,30 @@ static void second_pass(struct object_entry *obj)
base_obj->obj = obj;
base_obj->data = NULL;
find_unresolved_deltas(base_obj);
- display_progress(progress, nr_resolved_deltas);
+}
+
+static void *threaded_second_pass(void *arg)
+{
+ struct thread_local *data = get_thread_data();
+ for (;;) {
+ int i;
+ work_lock();
+ nr_resolved_deltas += data->nr_resolved_deltas;
+ display_progress(progress, nr_resolved_deltas);
+ data->nr_resolved_deltas = 0;
+ while (nr_processed < nr_objects &&
+ is_delta_type(objects[nr_processed].type))
+ nr_processed++;
+ if (nr_processed == nr_objects) {
+ work_unlock();
+ break;
+ }
+ i = nr_processed++;
+ work_unlock();
+
+ second_pass(&objects[i]);
+ }
+ return NULL;
}
/* Parse all objects and return the pack content SHA1 hash */
@@ -804,14 +895,26 @@ static void parse_pack_objects(unsigned char *sha1)
if (verbose)
progress = start_progress("Resolving deltas", nr_deltas);
- for (i = 0; i < nr_objects; i++) {
- struct object_entry *obj = &objects[i];
-
- if (is_delta_type(obj->type))
- continue;
- second_pass(obj);
+ nr_processed = 0;
+#ifndef NO_PTHREADS
+ if (nr_threads > 1) {
+ init_thread();
+ for (i = 1; i < nr_threads; i++) {
+ int ret = pthread_create(&thread_data[i].thread, NULL,
+ threaded_second_pass, NULL);
+ if (ret)
+ die("unable to create thread: %s", strerror(ret));
+ }
+ for (i = 1; i < nr_threads; i++) {
+ pthread_join(thread_data[i].thread, NULL);
+ thread_data[i].thread = 0;
+ }
+ cleanup_thread();
+ return;
}
+#endif
+ threaded_second_pass(thread_data);
}
static int write_compressed(struct sha1file *f, void *in, unsigned int size)
@@ -1017,6 +1120,17 @@ static int git_index_pack_config(const char *k, const char *v, void *cb)
die("bad pack.indexversion=%"PRIu32, opts->version);
return 0;
}
+ if (!strcmp(k, "pack.threads")) {
+ nr_threads = git_config_int(k, v);
+ if (nr_threads < 0)
+ die("invalid number of threads specified (%d)",
+ nr_threads);
+#ifdef NO_PTHREADS
+ if (nr_threads != 1)
+ warning("no threads support, ignoring %s", k);
+#endif
+ return 0;
+ }
return git_default_config(k, v, cb);
}
@@ -1175,6 +1289,16 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix)
keep_msg = "";
} else if (!prefixcmp(arg, "--keep=")) {
keep_msg = arg + 7;
+ } else if (!prefixcmp(arg, "--threads=")) {
+ char *end;
+ nr_threads = strtoul(arg+10, &end, 0);
+ if (!arg[10] || *end || nr_threads < 0)
+ usage(index_pack_usage);
+#ifdef NO_PTHREADS
+ if (nr_threads != 1)
+ warning("no threads support, "
+ "ignoring %s", arg);
+#endif
} else if (!prefixcmp(arg, "--pack_header=")) {
struct pack_header *hdr;
char *c;
@@ -1246,6 +1370,19 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix)
if (strict)
opts.flags |= WRITE_IDX_STRICT;
+#ifndef NO_PTHREADS
+ if (!nr_threads)
+ nr_threads = online_cpus();
+ /* reserve thread_data[0] for the main thread */
+ if (nr_threads > 1)
+ nr_threads++;
+#else
+ if (nr_threads != 1)
+ warning("no threads support, ignoring --threads");
+ nr_threads = 1;
+#endif
+ thread_data = xcalloc(nr_threads, sizeof(*thread_data));
+
curr_pack = open_pack_file(pack_name);
parse_pack_header();
objects = xcalloc(nr_objects + 1, sizeof(struct object_entry));
--
1.7.8.36.g69ee2
^ permalink raw reply related
* [PATCH v6 00/11] Column display
From: Nguyễn Thái Ngọc Duy @ 2012-02-25 11:41 UTC (permalink / raw)
To: git; +Cc: Junio C Hamano, Nguyễn Thái Ngọc Duy
Compared to v5 [1] this only has minor changes:
- drop term_columns() patch because master already has that
- style fixes
- incorporate Ramsay's fix in option parsing code
- make git_column_config() return either 0 or -1
[1] http://thread.gmane.org/gmane.comp.version-control.git/189758/focus=189875
Nguyễn Thái Ngọc Duy (11):
column: add API to print items in columns
Add git-column and column mode parsing
Stop starting pager recursively
column: add columnar layout
column: support columns with different widths
column: add column.ui for default column output settings
help: reuse print_columns() for help -a
branch: add --column
status: add --column
column: support piping stdout to external git-column process
tag: add --column
.gitignore | 1 +
Documentation/config.txt | 38 ++++
Documentation/git-branch.txt | 9 +
Documentation/git-column.txt | 53 +++++
Documentation/git-status.txt | 7 +
Documentation/git-tag.txt | 9 +
Makefile | 3 +
builtin.h | 1 +
builtin/branch.c | 32 +++-
builtin/column.c | 62 ++++++
builtin/commit.c | 6 +
builtin/tag.c | 26 ++-
column.c | 478 ++++++++++++++++++++++++++++++++++++++++++
column.h | 40 ++++
command-list.txt | 1 +
git.c | 1 +
help.c | 48 ++---
pager.c | 2 +-
parse-options.h | 2 +
t/t3200-branch.sh | 77 +++++++
t/t7004-tag.sh | 44 ++++
t/t7508-status.sh | 24 ++
t/t9002-column.sh | 161 ++++++++++++++
wt-status.c | 28 +++-
wt-status.h | 1 +
25 files changed, 1110 insertions(+), 44 deletions(-)
create mode 100644 Documentation/git-column.txt
create mode 100644 builtin/column.c
create mode 100644 column.c
create mode 100644 column.h
create mode 100755 t/t9002-column.sh
--
1.7.8.36.g69ee2
^ permalink raw reply
* [PATCH v6 01/11] column: add API to print items in columns
From: Nguyễn Thái Ngọc Duy @ 2012-02-25 11:41 UTC (permalink / raw)
To: git; +Cc: Junio C Hamano, Nguyễn Thái Ngọc Duy
In-Reply-To: <1330170078-29353-1-git-send-email-pclouds@gmail.com>
Simple code that print line-by-line and wants to columnize can do
this:
struct string_list list;
string_list_append(&list, ...);
string_list_append(&list, ...);
...
print_columns(&list, ...);
Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
Makefile | 2 ++
column.c | 39 +++++++++++++++++++++++++++++++++++++++
column.h | 19 +++++++++++++++++++
3 files changed, 60 insertions(+), 0 deletions(-)
create mode 100644 column.c
create mode 100644 column.h
diff --git a/Makefile b/Makefile
index a0de4e9..4e9501b 100644
--- a/Makefile
+++ b/Makefile
@@ -646,6 +646,7 @@ LIB_OBJS += bulk-checkin.o
LIB_OBJS += bundle.o
LIB_OBJS += cache-tree.o
LIB_OBJS += color.o
+LIB_OBJS += column.o
LIB_OBJS += combine-diff.o
LIB_OBJS += commit.o
LIB_OBJS += compat/obstack.o
@@ -2166,6 +2167,7 @@ builtin/prune.o builtin/reflog.o reachable.o: reachable.h
builtin/commit.o builtin/revert.o wt-status.o: wt-status.h
builtin/tar-tree.o archive-tar.o: tar.h
connect.o transport.o url.o http-backend.o: url.h
+column.o help.o pager.o: column.h
http-fetch.o http-walker.o remote-curl.o transport.o walker.o: walker.h
http.o http-walker.o http-push.o http-fetch.o remote-curl.o: http.h url.h
diff --git a/column.c b/column.c
new file mode 100644
index 0000000..742ae18
--- /dev/null
+++ b/column.c
@@ -0,0 +1,39 @@
+#include "cache.h"
+#include "column.h"
+#include "string-list.h"
+
+#define MODE(mode) ((mode) & COL_MODE)
+
+/* Display without layout when COL_ENABLED is not set */
+static void display_plain(const struct string_list *list,
+ const char *indent, const char *nl)
+{
+ int i;
+
+ for (i = 0; i < list->nr; i++)
+ printf("%s%s%s", indent, list->items[i].string, nl);
+}
+
+void print_columns(const struct string_list *list, unsigned int mode,
+ struct column_options *opts)
+{
+ const char *indent = "", *nl = "\n";
+ int padding = 1, width = term_columns();
+
+ if (!list->nr)
+ return;
+ if (opts) {
+ if (opts->indent)
+ indent = opts->indent;
+ if (opts->nl)
+ nl = opts->nl;
+ if (opts->width)
+ width = opts->width;
+ padding = opts->padding;
+ }
+ if (width <= 1 || !(mode & COL_ENABLED)) {
+ display_plain(list, indent, nl);
+ return;
+ }
+ die("BUG: invalid mode %d", MODE(mode));
+}
diff --git a/column.h b/column.h
new file mode 100644
index 0000000..8e4fdaa
--- /dev/null
+++ b/column.h
@@ -0,0 +1,19 @@
+#ifndef COLUMN_H
+#define COLUMN_H
+
+#define COL_MODE 0x000F
+#define COL_ENABLED (1 << 4)
+
+struct column_options {
+ int width;
+ int padding;
+ const char *indent;
+ const char *nl;
+};
+
+extern int term_columns(void);
+extern void print_columns(const struct string_list *list,
+ unsigned int mode,
+ struct column_options *opts);
+
+#endif
--
1.7.8.36.g69ee2
^ permalink raw reply related
* [PATCH v6 02/11] Add git-column and column mode parsing
From: Nguyễn Thái Ngọc Duy @ 2012-02-25 11:41 UTC (permalink / raw)
To: git; +Cc: Junio C Hamano, Nguyễn Thái Ngọc Duy
In-Reply-To: <1330170078-29353-1-git-send-email-pclouds@gmail.com>
A column option string consists of many token separated by either
space of commas. A token belongs to one of three groups:
- enabling: always, never and auto
- layout mode: to be implemented
- other tuning, which could be negated be prefix 'no'
A command line option without argument (e.g. --column) will enable
column output and reuse existing settings (layout mode and options..).
--no-column disables columnar output.
Thanks-to: Ramsay Jones <ramsay@ramsay1.demon.co.uk>
Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
.gitignore | 1 +
Documentation/git-column.txt | 49 +++++++++++++++
Makefile | 1 +
builtin.h | 1 +
builtin/column.c | 41 +++++++++++++
column.c | 133 ++++++++++++++++++++++++++++++++++++++++++
column.h | 11 ++++
command-list.txt | 1 +
git.c | 1 +
parse-options.h | 2 +
t/t9002-column.sh | 27 +++++++++
11 files changed, 268 insertions(+), 0 deletions(-)
create mode 100644 Documentation/git-column.txt
create mode 100644 builtin/column.c
create mode 100755 t/t9002-column.sh
diff --git a/.gitignore b/.gitignore
index 87fcc5f..2540264 100644
--- a/.gitignore
+++ b/.gitignore
@@ -26,6 +26,7 @@
/git-cherry-pick
/git-clean
/git-clone
+/git-column
/git-commit
/git-commit-tree
/git-config
diff --git a/Documentation/git-column.txt b/Documentation/git-column.txt
new file mode 100644
index 0000000..508b85f
--- /dev/null
+++ b/Documentation/git-column.txt
@@ -0,0 +1,49 @@
+git-column(1)
+=============
+
+NAME
+----
+git-column - Display data in columns
+
+SYNOPSIS
+--------
+[verse]
+'git column' [--mode=<mode> | --rawmode=<n>] [--width=<width>]
+ [--indent=<string>] [--nl=<string>] [--pading=<n>]
+
+DESCRIPTION
+-----------
+This command formats its input into multiple columns.
+
+OPTIONS
+-------
+--mode=<mode>::
+ Specify layout mode. See configuration variable column.ui for option
+ syntax.
+
+--rawmode=<n>::
+ Same as --mode but take mode encoded as a number. This is mainly used
+ by other commands that have already parsed layout mode.
+
+--width=<width>::
+ Specify the terminal width. By default 'git column' will detect the
+ terminal width, or fall back to 80 if it is unable to do so.
+
+--indent=<string>::
+ String to be printed at the beginning of each line.
+
+--nl=<N>::
+ String to be printed at the end of each line,
+ including newline character.
+
+--padding=<N>::
+ The number of spaces between columns. One space by default.
+
+
+Author
+------
+Written by Nguyen Thai Ngoc Duy <pclouds@gmail.com>
+
+GIT
+---
+Part of the linkgit:git[1] suite
diff --git a/Makefile b/Makefile
index 4e9501b..0998f0d 100644
--- a/Makefile
+++ b/Makefile
@@ -775,6 +775,7 @@ BUILTIN_OBJS += builtin/checkout-index.o
BUILTIN_OBJS += builtin/checkout.o
BUILTIN_OBJS += builtin/clean.o
BUILTIN_OBJS += builtin/clone.o
+BUILTIN_OBJS += builtin/column.o
BUILTIN_OBJS += builtin/commit-tree.o
BUILTIN_OBJS += builtin/commit.o
BUILTIN_OBJS += builtin/config.o
diff --git a/builtin.h b/builtin.h
index 857b9c8..338f540 100644
--- a/builtin.h
+++ b/builtin.h
@@ -61,6 +61,7 @@ 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_clone(int argc, const char **argv, const char *prefix);
extern int cmd_clean(int argc, const char **argv, const char *prefix);
+extern int cmd_column(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_config(int argc, const char **argv, const char *prefix);
diff --git a/builtin/column.c b/builtin/column.c
new file mode 100644
index 0000000..3b0f74e
--- /dev/null
+++ b/builtin/column.c
@@ -0,0 +1,41 @@
+#include "builtin.h"
+#include "cache.h"
+#include "strbuf.h"
+#include "parse-options.h"
+#include "string-list.h"
+#include "column.h"
+
+static const char * const builtin_column_usage[] = {
+ "git column [options]",
+ NULL
+};
+static unsigned int colopts;
+
+int cmd_column(int argc, const char **argv, const char *prefix)
+{
+ struct string_list list = STRING_LIST_INIT_DUP;
+ struct strbuf sb = STRBUF_INIT;
+ struct column_options copts;
+ struct option options[] = {
+ OPT_COLUMN(0, "mode", &colopts, "layout to use"),
+ OPT_INTEGER(0, "rawmode", &colopts, "layout to use"),
+ OPT_INTEGER(0, "width", &copts.width, "Maximum width"),
+ OPT_STRING(0, "indent", &copts.indent, "string", "Padding space on left border"),
+ OPT_INTEGER(0, "nl", &copts.nl, "Padding space on right border"),
+ OPT_INTEGER(0, "padding", &copts.padding, "Padding space between columns"),
+ OPT_END()
+ };
+
+ memset(&copts, 0, sizeof(copts));
+ copts.width = term_columns();
+ copts.padding = 1;
+ argc = parse_options(argc, argv, "", options, builtin_column_usage, 0);
+ if (argc)
+ usage_with_options(builtin_column_usage, options);
+
+ while (!strbuf_getline(&sb, stdin, '\n'))
+ string_list_append(&list, sb.buf);
+
+ print_columns(&list, colopts, &copts);
+ return 0;
+}
diff --git a/column.c b/column.c
index 742ae18..e62edf7 100644
--- a/column.c
+++ b/column.c
@@ -1,6 +1,7 @@
#include "cache.h"
#include "column.h"
#include "string-list.h"
+#include "parse-options.h"
#define MODE(mode) ((mode) & COL_MODE)
@@ -37,3 +38,135 @@ void print_columns(const struct string_list *list, unsigned int mode,
}
die("BUG: invalid mode %d", MODE(mode));
}
+
+struct colopt {
+ enum {
+ ENABLE,
+ MODE,
+ OPTION
+ } type;
+ const char *name;
+ int value;
+};
+
+/*
+ * Set COL_ENABLED and COL_ENABLED_SET. If 'set' is -1, check if
+ * stdout is tty.
+ */
+static int set_enable_bit(unsigned int *mode, int set, int stdout_is_tty)
+{
+ if (set < 0) { /* auto */
+ if (stdout_is_tty < 0)
+ stdout_is_tty = isatty(1);
+ set = stdout_is_tty || (pager_in_use() && pager_use_color);
+ }
+ if (set)
+ *mode = *mode | COL_ENABLED | COL_ENABLED_SET;
+ else
+ *mode = (*mode & ~COL_ENABLED) | COL_ENABLED_SET;
+ return 0;
+}
+
+/*
+ * Set COL_MODE_*. mode is intially copied from column.ui. If
+ * COL_ENABLED_SET is not set, then neither 'always', 'never' nor
+ * 'auto' has been used. Default to 'always'.
+ */
+static int set_mode(unsigned int *mode, unsigned int value)
+{
+ *mode = (*mode & ~COL_MODE) | value;
+ if (!(*mode & COL_ENABLED_SET))
+ *mode |= COL_ENABLED | COL_ENABLED_SET;
+
+ return 0;
+}
+
+/* Set or unset other COL_* */
+static int set_option(unsigned int *mode, unsigned int opt, int set)
+{
+ if (set)
+ *mode |= opt;
+ else
+ *mode &= ~opt;
+ return 0;
+}
+
+static int parse_option(const char *arg, int len,
+ unsigned int *mode, int stdout_is_tty)
+{
+ struct colopt opts[] = {
+ { ENABLE, "always", 1 },
+ { ENABLE, "never", 0 },
+ { ENABLE, "auto", -1 },
+ };
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(opts); i++) {
+ int set = 1, arg_len = len, name_len;
+ const char *arg_str = arg;
+
+ if (opts[i].type == OPTION) {
+ if (arg_len > 2 && !strncmp(arg_str, "no", 2)) {
+ arg_str += 2;
+ arg_len -= 2;
+ set = 0;
+ } else {
+ set = 1;
+ }
+ }
+
+ name_len = strlen(opts[i].name);
+ if (arg_len != name_len ||
+ strncmp(arg_str, opts[i].name, name_len))
+ continue;
+
+ switch (opts[i].type) {
+ case ENABLE:
+ return set_enable_bit(mode, opts[i].value,
+ stdout_is_tty);
+ case MODE:
+ return set_mode(mode, opts[i].value);
+ case OPTION:
+ return set_option(mode, opts[i].value, set);
+ default:
+ die("BUG: Unknown option type %d", opts[i].type);
+ }
+ }
+
+ return error("unsupported style '%s'", arg);
+}
+
+int git_config_column(unsigned int *mode, const char *value,
+ int stdout_is_tty)
+{
+ const char *sep = " ,";
+
+ while (*value) {
+ int len = strcspn(value, sep);
+ if (len) {
+ if (parse_option(value, len, mode, stdout_is_tty))
+ return -1;
+
+ value += len;
+ }
+ value += strspn(value, sep);
+ }
+ return 0;
+}
+
+int parseopt_column_callback(const struct option *opt,
+ const char *arg, int unset)
+{
+ unsigned int *mode = opt->value;
+ *mode |= COL_PARSEOPT;
+ if (unset) {
+ *mode = (*mode & ~COL_ENABLED) | COL_ENABLED_SET;
+ return 0;
+ }
+ if (arg)
+ return git_config_column(mode, arg, -1);
+
+ /* no arg, turn it on */
+ *mode |= COL_ENABLED | COL_ENABLED_SET;
+ return 0;
+}
diff --git a/column.h b/column.h
index 8e4fdaa..67b1c4f 100644
--- a/column.h
+++ b/column.h
@@ -3,6 +3,11 @@
#define COL_MODE 0x000F
#define COL_ENABLED (1 << 4)
+#define COL_ENABLED_SET (1 << 5) /* Has COL_ENABLED been set by config? */
+#define COL_PARSEOPT (1 << 8) /* --column is given */
+
+#define explicitly_enable_column(c) \
+ (((c) & (COL_PARSEOPT | COL_ENABLED)) == (COL_PARSEOPT | COL_ENABLED))
struct column_options {
int width;
@@ -15,5 +20,11 @@ extern int term_columns(void);
extern void print_columns(const struct string_list *list,
unsigned int mode,
struct column_options *opts);
+extern int git_config_column(unsigned int *mode, const char *value,
+ int stdout_is_tty);
+
+struct option;
+extern int parseopt_column_callback(const struct option *opt,
+ const char *arg, int unset);
#endif
diff --git a/command-list.txt b/command-list.txt
index a36ee9b..fe06f15 100644
--- a/command-list.txt
+++ b/command-list.txt
@@ -20,6 +20,7 @@ git-cherry-pick mainporcelain
git-citool mainporcelain
git-clean mainporcelain
git-clone mainporcelain common
+git-column purehelpers
git-commit mainporcelain common
git-commit-tree plumbingmanipulators
git-config ancillarymanipulators
diff --git a/git.c b/git.c
index 3805616..419e3cc 100644
--- a/git.c
+++ b/git.c
@@ -348,6 +348,7 @@ static void handle_internal_command(int argc, const char **argv)
{ "cherry-pick", cmd_cherry_pick, RUN_SETUP | NEED_WORK_TREE },
{ "clean", cmd_clean, RUN_SETUP | NEED_WORK_TREE },
{ "clone", cmd_clone },
+ { "column", cmd_column },
{ "commit", cmd_commit, RUN_SETUP | NEED_WORK_TREE },
{ "commit-tree", cmd_commit_tree, RUN_SETUP },
{ "config", cmd_config, RUN_SETUP_GENTLY },
diff --git a/parse-options.h b/parse-options.h
index 2e811dc..56fcafd 100644
--- a/parse-options.h
+++ b/parse-options.h
@@ -238,5 +238,7 @@ extern int parse_opt_noop_cb(const struct option *, const char *, int);
PARSE_OPT_OPTARG, &parse_opt_abbrev_cb, 0 }
#define OPT__COLOR(var, h) \
OPT_COLOR_FLAG(0, "color", (var), (h))
+#define OPT_COLUMN(s, l, v, h) \
+ { OPTION_CALLBACK, (s), (l), (v), "style", (h), PARSE_OPT_OPTARG, parseopt_column_callback }
#endif
diff --git a/t/t9002-column.sh b/t/t9002-column.sh
new file mode 100755
index 0000000..b0b6d62
--- /dev/null
+++ b/t/t9002-column.sh
@@ -0,0 +1,27 @@
+#!/bin/sh
+
+test_description='git column'
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+ cat >lista <<\EOF
+one
+two
+three
+four
+five
+six
+seven
+eight
+nine
+ten
+eleven
+EOF
+'
+
+test_expect_success 'never' '
+ git column --mode=never <lista >actual &&
+ test_cmp lista actual
+'
+
+test_done
--
1.7.8.36.g69ee2
^ permalink raw reply related
* [PATCH v6 03/11] Stop starting pager recursively
From: Nguyễn Thái Ngọc Duy @ 2012-02-25 11:41 UTC (permalink / raw)
To: git; +Cc: Junio C Hamano, Nguyễn Thái Ngọc Duy
In-Reply-To: <1330170078-29353-1-git-send-email-pclouds@gmail.com>
git-column can be used as a pager for other git commands, something
like this:
GIT_PAGER="git -p column --mode='dense color'" git -p branch
The problem with this is that "git -p column" also has $GIT_PAGER
set so the pager runs itself again as a pager, then again and again.
Stop this.
Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
| 2 +-
1 files changed, 1 insertions(+), 1 deletions(-)
--git a/pager.c b/pager.c
index 05584de..4dcb08d 100644
--- a/pager.c
+++ b/pager.c
@@ -73,7 +73,7 @@ void setup_pager(void)
{
const char *pager = git_pager(isatty(1));
- if (!pager)
+ if (!pager || pager_in_use())
return;
/*
--
1.7.8.36.g69ee2
^ permalink raw reply related
* [PATCH v6 04/11] column: add columnar layout
From: Nguyễn Thái Ngọc Duy @ 2012-02-25 11:41 UTC (permalink / raw)
To: git; +Cc: Junio C Hamano, Nguyễn Thái Ngọc Duy
In-Reply-To: <1330170078-29353-1-git-send-email-pclouds@gmail.com>
COL_MODE_COLUMN and COL_MODE_ROW fill column by column (or row by row
respectively), given the terminal width and how many space between
columns.
Strings are supposed to be in UTF-8. If strings contain ANSI escape
strings, COL_ANSI must be specified for correct length calculation.
Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
column.c | 131 ++++++++++++++++++++++++++++++++++++++++++++++++++++-
column.h | 3 +
t/t9002-column.sh | 86 +++++++++++++++++++++++++++++++++++
3 files changed, 219 insertions(+), 1 deletions(-)
diff --git a/column.c b/column.c
index e62edf7..6dca3b8 100644
--- a/column.c
+++ b/column.c
@@ -2,8 +2,66 @@
#include "column.h"
#include "string-list.h"
#include "parse-options.h"
+#include "utf8.h"
#define MODE(mode) ((mode) & COL_MODE)
+#define XY2LINEAR(d, x, y) (MODE((d)->mode) == COL_MODE_COLUMN ? \
+ (x) * (d)->rows + (y) : \
+ (y) * (d)->cols + (x))
+
+struct column_data {
+ const struct string_list *list; /* list of all cells */
+ int mode; /* COL_MODE */
+ int total_width; /* terminal width */
+ int padding; /* cell padding */
+ const char *indent; /* left most column indentation */
+ const char *nl;
+
+ int rows, cols;
+ int *len; /* cell length */
+};
+
+/* return length of 's' in letters, ANSI escapes stripped */
+static int item_length(int mode, const char *s)
+{
+ int len, i = 0;
+ struct strbuf str = STRBUF_INIT;
+
+ if (!(mode & COL_ANSI))
+ return utf8_strwidth(s);
+
+ strbuf_addstr(&str, s);
+ while ((s = strstr(str.buf + i, "\033[")) != NULL) {
+ int len = strspn(s + 2, "0123456789;");
+ i = s - str.buf;
+ strbuf_remove(&str, i, len + 3); /* \033[<len><func char> */
+ }
+ len = utf8_strwidth(str.buf);
+ strbuf_release(&str);
+ return len;
+}
+
+/*
+ * Calculate cell width, rows and cols for a table of equal cells, given
+ * table width and how many spaces between cells.
+ */
+static void layout(struct column_data *data, int *width)
+{
+ int i;
+
+ *width = 0;
+ for (i = 0; i < data->list->nr; i++)
+ if (*width < data->len[i])
+ *width = data->len[i];
+
+ *width += data->padding;
+
+ data->cols = (data->total_width - strlen(data->indent)) / *width;
+ if (data->cols == 0)
+ data->cols = 1;
+
+ data->rows = DIV_ROUND_UP(data->list->nr, data->cols);
+}
/* Display without layout when COL_ENABLED is not set */
static void display_plain(const struct string_list *list,
@@ -15,6 +73,65 @@ static void display_plain(const struct string_list *list,
printf("%s%s%s", indent, list->items[i].string, nl);
}
+/* Print a cell to stdout with all necessary leading/traling space */
+static int display_cell(struct column_data *data, int initial_width,
+ const char *empty_cell, int x, int y)
+{
+ int i, len, newline;
+
+ i = XY2LINEAR(data, x, y);
+ if (i >= data->list->nr)
+ return -1;
+ len = data->len[i];
+ if (MODE(data->mode) == COL_MODE_COLUMN)
+ newline = i + data->rows >= data->list->nr;
+ else
+ newline = x == data->cols - 1 || i == data->list->nr - 1;
+
+ printf("%s%s%s",
+ x == 0 ? data->indent : "",
+ data->list->items[i].string,
+ newline ? data->nl : empty_cell + len);
+ return 0;
+}
+
+/* Display COL_MODE_COLUMN or COL_MODE_ROW */
+static void display_table(const struct string_list *list,
+ int mode, int total_width,
+ int padding, const char *indent,
+ const char *nl)
+{
+ struct column_data data;
+ int x, y, i, initial_width;
+ char *empty_cell;
+
+ memset(&data, 0, sizeof(data));
+ data.list = list;
+ data.mode = mode;
+ data.total_width = total_width;
+ data.padding = padding;
+ data.indent = indent;
+ data.nl = nl;
+
+ data.len = xmalloc(sizeof(*data.len) * list->nr);
+ for (i = 0; i < list->nr; i++)
+ data.len[i] = item_length(mode, list->items[i].string);
+
+ layout(&data, &initial_width);
+
+ empty_cell = xmalloc(initial_width + 1);
+ memset(empty_cell, ' ', initial_width);
+ empty_cell[initial_width] = '\0';
+ for (y = 0; y < data.rows; y++) {
+ for (x = 0; x < data.cols; x++)
+ if (display_cell(&data, initial_width, empty_cell, x, y))
+ break;
+ }
+
+ free(data.len);
+ free(empty_cell);
+}
+
void print_columns(const struct string_list *list, unsigned int mode,
struct column_options *opts)
{
@@ -36,7 +153,16 @@ void print_columns(const struct string_list *list, unsigned int mode,
display_plain(list, indent, nl);
return;
}
- die("BUG: invalid mode %d", MODE(mode));
+
+ switch (MODE(mode)) {
+ case COL_MODE_ROW:
+ case COL_MODE_COLUMN:
+ display_table(list, mode, width, padding, indent, nl);
+ break;
+
+ default:
+ die("BUG: invalid mode %d", MODE(mode));
+ }
}
struct colopt {
@@ -98,6 +224,9 @@ static int parse_option(const char *arg, int len,
{ ENABLE, "always", 1 },
{ ENABLE, "never", 0 },
{ ENABLE, "auto", -1 },
+ { MODE, "column", COL_MODE_COLUMN },
+ { MODE, "row", COL_MODE_ROW },
+ { OPTION, "color", COL_ANSI },
};
int i;
diff --git a/column.h b/column.h
index 67b1c4f..d9d27c6 100644
--- a/column.h
+++ b/column.h
@@ -2,8 +2,11 @@
#define COLUMN_H
#define COL_MODE 0x000F
+#define COL_MODE_COLUMN 0 /* Fill columns before rows */
+#define COL_MODE_ROW 1 /* Fill rows before columns */
#define COL_ENABLED (1 << 4)
#define COL_ENABLED_SET (1 << 5) /* Has COL_ENABLED been set by config? */
+#define COL_ANSI (1 << 6) /* Remove ANSI escapes from string length */
#define COL_PARSEOPT (1 << 8) /* --column is given */
#define explicitly_enable_column(c) \
diff --git a/t/t9002-column.sh b/t/t9002-column.sh
index b0b6d62..cffb029 100755
--- a/t/t9002-column.sh
+++ b/t/t9002-column.sh
@@ -24,4 +24,90 @@ test_expect_success 'never' '
test_cmp lista actual
'
+test_expect_success '80 columns' '
+ cat >expected <<\EOF &&
+one two three four five six seven eight nine ten eleven
+EOF
+ COLUMNS=80 git column --mode=column <lista >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'COLUMNS = 1' '
+ cat >expected <<\EOF &&
+one
+two
+three
+four
+five
+six
+seven
+eight
+nine
+ten
+eleven
+EOF
+ COLUMNS=1 git column --mode=column <lista >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'width = 1' '
+ git column --mode=column --width=1 <lista >actual &&
+ test_cmp expected actual
+'
+
+COLUMNS=20
+export COLUMNS
+
+test_expect_success '20 columns' '
+ cat >expected <<\EOF &&
+one seven
+two eight
+three nine
+four ten
+five eleven
+six
+EOF
+ git column --mode=column <lista >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success '20 columns, padding 2' '
+ cat >expected <<\EOF &&
+one seven
+two eight
+three nine
+four ten
+five eleven
+six
+EOF
+ git column --mode=column --padding 2 <lista >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success '20 columns, indented' '
+ cat >expected <<\EOF &&
+ one seven
+ two eight
+ three nine
+ four ten
+ five eleven
+ six
+EOF
+ git column --mode=column --indent=" " <lista >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success '20 columns, row first' '
+ cat >expected <<\EOF &&
+one two
+three four
+five six
+seven eight
+nine ten
+eleven
+EOF
+ git column --mode=row <lista >actual &&
+ test_cmp expected actual
+'
+
test_done
--
1.7.8.36.g69ee2
^ permalink raw reply related
* [PATCH v6 05/11] column: support columns with different widths
From: Nguyễn Thái Ngọc Duy @ 2012-02-25 11:41 UTC (permalink / raw)
To: git; +Cc: Junio C Hamano, Nguyễn Thái Ngọc Duy
In-Reply-To: <1330170078-29353-1-git-send-email-pclouds@gmail.com>
Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
column.c | 80 +++++++++++++++++++++++++++++++++++++++++++++++++++++
column.h | 2 +
t/t9002-column.sh | 48 +++++++++++++++++++++++++++++++
3 files changed, 130 insertions(+), 0 deletions(-)
diff --git a/column.c b/column.c
index 6dca3b8..3c77997 100644
--- a/column.c
+++ b/column.c
@@ -19,6 +19,7 @@ struct column_data {
int rows, cols;
int *len; /* cell length */
+ int *width; /* index to the longest row in column */
};
/* return length of 's' in letters, ANSI escapes stripped */
@@ -63,6 +64,69 @@ static void layout(struct column_data *data, int *width)
data->rows = DIV_ROUND_UP(data->list->nr, data->cols);
}
+static void compute_column_width(struct column_data *data)
+{
+ int i, x, y;
+ for (x = 0; x < data->cols; x++) {
+ data->width[x] = XY2LINEAR(data, x, 0);
+ for (y = 0; y < data->rows; y++) {
+ i = XY2LINEAR(data, x, y);
+ if (i >= data->list->nr)
+ continue;
+ if (data->len[data->width[x]] < data->len[i])
+ data->width[x] = i;
+ }
+ }
+}
+
+/*
+ * Shrink all columns by shortening them one row each time (and adding
+ * more columns along the way). Hopefully the longest cell will be
+ * moved to the next column, column is shrunk so we have more space
+ * for new columns. The process ends when the whole thing no longer
+ * fits in data->total_width.
+ */
+static void shrink_columns(struct column_data *data)
+{
+ int x, y, total_width, cols, rows;
+
+ data->width = xrealloc(data->width,
+ sizeof(*data->width) * data->cols);
+ for (x = 0; x < data->cols; x++) {
+ data->width[x] = 0;
+ for (y = 0; y < data->rows; y++) {
+ int len1 = data->len[data->width[x]];
+ int len2 = data->len[XY2LINEAR(data, x, y)];
+ if (len1 < len2)
+ data->width[x] = y;
+ }
+ }
+
+ while (data->rows > 1) {
+ rows = data->rows;
+ cols = data->cols;
+
+ data->rows--;
+ data->cols = DIV_ROUND_UP(data->list->nr, data->rows);
+ if (data->cols != cols)
+ data->width = xrealloc(data->width, sizeof(*data->width) * data->cols);
+
+ compute_column_width(data);
+
+ total_width = strlen(data->indent);
+ for (x = 0; x < data->cols; x++) {
+ total_width += data->len[data->width[x]];
+ total_width += data->padding;
+ }
+ if (total_width > data->total_width) {
+ data->rows = rows;
+ data->cols = cols;
+ compute_column_width(data);
+ break;
+ }
+ }
+}
+
/* Display without layout when COL_ENABLED is not set */
static void display_plain(const struct string_list *list,
const char *indent, const char *nl)
@@ -82,7 +146,18 @@ static int display_cell(struct column_data *data, int initial_width,
i = XY2LINEAR(data, x, y);
if (i >= data->list->nr)
return -1;
+
len = data->len[i];
+ if (data->width && data->len[data->width[x]] < initial_width) {
+ /*
+ * empty_cell has initial_width chars, if real column
+ * is narrower, increase len a bit so we fill less
+ * space.
+ */
+ len += initial_width - data->len[data->width[x]];
+ len -= data->padding;
+ }
+
if (MODE(data->mode) == COL_MODE_COLUMN)
newline = i + data->rows >= data->list->nr;
else
@@ -119,6 +194,9 @@ static void display_table(const struct string_list *list,
layout(&data, &initial_width);
+ if (mode & COL_DENSE)
+ shrink_columns(&data);
+
empty_cell = xmalloc(initial_width + 1);
memset(empty_cell, ' ', initial_width);
empty_cell[initial_width] = '\0';
@@ -129,6 +207,7 @@ static void display_table(const struct string_list *list,
}
free(data.len);
+ free(data.width);
free(empty_cell);
}
@@ -227,6 +306,7 @@ static int parse_option(const char *arg, int len,
{ MODE, "column", COL_MODE_COLUMN },
{ MODE, "row", COL_MODE_ROW },
{ OPTION, "color", COL_ANSI },
+ { OPTION, "dense", COL_DENSE },
};
int i;
diff --git a/column.h b/column.h
index d9d27c6..eb03c6c 100644
--- a/column.h
+++ b/column.h
@@ -7,6 +7,8 @@
#define COL_ENABLED (1 << 4)
#define COL_ENABLED_SET (1 << 5) /* Has COL_ENABLED been set by config? */
#define COL_ANSI (1 << 6) /* Remove ANSI escapes from string length */
+#define COL_DENSE (1 << 7) /* Shrink columns when possible,
+ making space for more columns */
#define COL_PARSEOPT (1 << 8) /* --column is given */
#define explicitly_enable_column(c) \
diff --git a/t/t9002-column.sh b/t/t9002-column.sh
index cffb029..fe7a30e 100755
--- a/t/t9002-column.sh
+++ b/t/t9002-column.sh
@@ -71,6 +71,30 @@ EOF
test_cmp expected actual
'
+test_expect_success '20 columns, nodense' '
+ cat >expected <<\EOF &&
+one seven
+two eight
+three nine
+four ten
+five eleven
+six
+EOF
+ git column --mode=column,nodense < lista > actual &&
+ test_cmp expected actual
+'
+
+test_expect_success '20 columns, dense' '
+ cat >expected <<\EOF &&
+one five nine
+two six ten
+three seven eleven
+four eight
+EOF
+ git column --mode=column,dense < lista > actual &&
+ test_cmp expected actual
+'
+
test_expect_success '20 columns, padding 2' '
cat >expected <<\EOF &&
one seven
@@ -110,4 +134,28 @@ EOF
test_cmp expected actual
'
+test_expect_success '20 columns, row first, nodense' '
+ cat >expected <<\EOF &&
+one two
+three four
+five six
+seven eight
+nine ten
+eleven
+EOF
+ git column --mode=row,nodense <lista >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success '20 columns, row first, dense' '
+ cat >expected <<\EOF &&
+one two three
+four five six
+seven eight nine
+ten eleven
+EOF
+ git column --mode=row,dense <lista >actual &&
+ test_cmp expected actual
+'
+
test_done
--
1.7.8.36.g69ee2
^ permalink raw reply related
* [PATCH v6 07/11] help: reuse print_columns() for help -a
From: Nguyễn Thái Ngọc Duy @ 2012-02-25 11:41 UTC (permalink / raw)
To: git; +Cc: Junio C Hamano, Nguyễn Thái Ngọc Duy
In-Reply-To: <1330170078-29353-1-git-send-email-pclouds@gmail.com>
Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
help.c | 48 ++++++++++++++----------------------------------
1 files changed, 14 insertions(+), 34 deletions(-)
diff --git a/help.c b/help.c
index 14eefc9..d6d2e19 100644
--- a/help.c
+++ b/help.c
@@ -4,6 +4,8 @@
#include "levenshtein.h"
#include "help.h"
#include "common-cmds.h"
+#include "string-list.h"
+#include "column.h"
void add_cmdname(struct cmdnames *cmds, const char *name, int len)
{
@@ -70,31 +72,18 @@ void exclude_cmds(struct cmdnames *cmds, struct cmdnames *excludes)
cmds->cnt = cj;
}
-static void pretty_print_string_list(struct cmdnames *cmds, int longest)
+static void pretty_print_string_list(struct cmdnames *cmds)
{
- int cols = 1, rows;
- int space = longest + 1; /* min 1 SP between words */
- int max_cols = term_columns() - 1; /* don't print *on* the edge */
- int i, j;
-
- if (space < max_cols)
- cols = max_cols / space;
- rows = DIV_ROUND_UP(cmds->cnt, cols);
-
- for (i = 0; i < rows; i++) {
- printf(" ");
+ struct string_list list = STRING_LIST_INIT_NODUP;
+ struct column_options copts;
+ int i;
- for (j = 0; j < cols; j++) {
- int n = j * rows + i;
- int size = space;
- if (n >= cmds->cnt)
- break;
- if (j == cols-1 || n + rows >= cmds->cnt)
- size = 1;
- printf("%-*s", size, cmds->names[n]->name);
- }
- putchar('\n');
- }
+ for (i = 0; i < cmds->cnt; i++)
+ string_list_append(&list, cmds->names[i]->name);
+ memset(&copts, 0, sizeof(copts));
+ copts.indent = " ";
+ print_columns(&list, COL_MODE_COLUMN | COL_ENABLED, &copts);
+ string_list_clear(&list, 0);
}
static int is_executable(const char *name)
@@ -206,22 +195,13 @@ void load_command_list(const char *prefix,
void list_commands(const char *title, struct cmdnames *main_cmds,
struct cmdnames *other_cmds)
{
- int i, longest = 0;
-
- for (i = 0; i < main_cmds->cnt; i++)
- if (longest < main_cmds->names[i]->len)
- longest = main_cmds->names[i]->len;
- for (i = 0; i < other_cmds->cnt; i++)
- if (longest < other_cmds->names[i]->len)
- longest = other_cmds->names[i]->len;
-
if (main_cmds->cnt) {
const char *exec_path = git_exec_path();
printf("available %s in '%s'\n", title, exec_path);
printf("----------------");
mput_char('-', strlen(title) + strlen(exec_path));
putchar('\n');
- pretty_print_string_list(main_cmds, longest);
+ pretty_print_string_list(main_cmds);
putchar('\n');
}
@@ -230,7 +210,7 @@ void list_commands(const char *title, struct cmdnames *main_cmds,
printf("---------------------------------------");
mput_char('-', strlen(title));
putchar('\n');
- pretty_print_string_list(other_cmds, longest);
+ pretty_print_string_list(other_cmds);
putchar('\n');
}
}
--
1.7.8.36.g69ee2
^ permalink raw reply related
* [PATCH v6 06/11] column: add column.ui for default column output settings
From: Nguyễn Thái Ngọc Duy @ 2012-02-25 11:41 UTC (permalink / raw)
To: git; +Cc: Junio C Hamano, Nguyễn Thái Ngọc Duy
In-Reply-To: <1330170078-29353-1-git-send-email-pclouds@gmail.com>
Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
Documentation/config.txt | 26 ++++++++++++++++++++++++++
Documentation/git-column.txt | 6 +++++-
builtin/column.c | 21 +++++++++++++++++++++
column.c | 28 ++++++++++++++++++++++++++++
column.h | 2 ++
5 files changed, 82 insertions(+), 1 deletions(-)
diff --git a/Documentation/config.txt b/Documentation/config.txt
index abeb82b..5216598 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -821,6 +821,32 @@ color.ui::
`never` if you prefer git commands not to use color unless enabled
explicitly with some other configuration or the `--color` option.
+column.ui::
+ Specify whether supported commands should output in columns.
+ This variable consists of a list of tokens separated by spaces
+ or commas:
++
+--
+`always`;;
+ always show in columns
+`never`;;
+ never show in columns
+`auto`;;
+ show in columns if the output is to the terminal
+`column`;;
+ fill columns before rows (default)
+`row`;;
+ fill rows before columns
+`dense`;;
+ make unequal size columns to utilize more space
+`nodense`;;
+ make equal size columns
+`color`;;
+ input contains ANSI escape sequence for coloring
+--
++
+ This option defaults to 'never'.
+
commit.status::
A boolean to enable/disable inclusion of status information in the
commit message template when using an editor to prepare the commit
diff --git a/Documentation/git-column.txt b/Documentation/git-column.txt
index 508b85f..94fd7ac 100644
--- a/Documentation/git-column.txt
+++ b/Documentation/git-column.txt
@@ -8,7 +8,7 @@ git-column - Display data in columns
SYNOPSIS
--------
[verse]
-'git column' [--mode=<mode> | --rawmode=<n>] [--width=<width>]
+'git column' [--command=<name>] [--[raw]mode=<mode>] [--width=<width>]
[--indent=<string>] [--nl=<string>] [--pading=<n>]
DESCRIPTION
@@ -17,6 +17,10 @@ This command formats its input into multiple columns.
OPTIONS
-------
+--command=<name>::
+ Look up layout mode using configuration variable column.<name> and
+ column.ui.
+
--mode=<mode>::
Specify layout mode. See configuration variable column.ui for option
syntax.
diff --git a/builtin/column.c b/builtin/column.c
index 3b0f74e..102d71b 100644
--- a/builtin/column.c
+++ b/builtin/column.c
@@ -11,12 +11,19 @@ static const char * const builtin_column_usage[] = {
};
static unsigned int colopts;
+static int column_config(const char *var, const char *value, void *cb)
+{
+ return git_column_config(var, value, cb, &colopts);
+}
+
int cmd_column(int argc, const char **argv, const char *prefix)
{
struct string_list list = STRING_LIST_INIT_DUP;
struct strbuf sb = STRBUF_INIT;
struct column_options copts;
+ const char *command = NULL, *real_command = NULL;
struct option options[] = {
+ OPT_STRING(0, "command", &real_command, "name", "lookup config vars"),
OPT_COLUMN(0, "mode", &colopts, "layout to use"),
OPT_INTEGER(0, "rawmode", &colopts, "layout to use"),
OPT_INTEGER(0, "width", &copts.width, "Maximum width"),
@@ -26,6 +33,15 @@ int cmd_column(int argc, const char **argv, const char *prefix)
OPT_END()
};
+ /* This one is special and must be the first one */
+ if (argc > 1 && !prefixcmp(argv[1], "--command=")) {
+ int nonitok = 0;
+ setup_git_directory_gently(&nonitok);
+
+ command = argv[1] + 10;
+ git_config(column_config, (void *)command);
+ }
+
memset(&copts, 0, sizeof(copts));
copts.width = term_columns();
copts.padding = 1;
@@ -33,6 +49,11 @@ int cmd_column(int argc, const char **argv, const char *prefix)
if (argc)
usage_with_options(builtin_column_usage, options);
+ if (real_command || command) {
+ if (!real_command || !command || strcmp(real_command, command))
+ die(_("--command must be the first argument"));
+ }
+
while (!strbuf_getline(&sb, stdin, '\n'))
string_list_append(&list, sb.buf);
diff --git a/column.c b/column.c
index 3c77997..94fd1a1 100644
--- a/column.c
+++ b/column.c
@@ -2,6 +2,7 @@
#include "column.h"
#include "string-list.h"
#include "parse-options.h"
+#include "color.h"
#include "utf8.h"
#define MODE(mode) ((mode) & COL_MODE)
@@ -363,6 +364,33 @@ int git_config_column(unsigned int *mode, const char *value,
return 0;
}
+static int column_config(const char *var, const char *value,
+ const char *key, unsigned int *colopts)
+{
+ if (git_config_column(colopts, value, -1))
+ return error("invalid %s mode %s", key, value);
+ return 0;
+}
+
+int git_column_config(const char *var, const char *value,
+ const char *command, unsigned int *colopts)
+{
+ if (!strcmp(var, "column.ui"))
+ return column_config(var, value, "column.ui", colopts);
+
+ if (command) {
+ struct strbuf sb = STRBUF_INIT;
+ int ret = 0;
+ strbuf_addf(&sb, "column.%s", command);
+ if (!strcmp(var, sb.buf))
+ ret = column_config(var, value, sb.buf, colopts);
+ strbuf_release(&sb);
+ return ret;
+ }
+
+ return 0;
+}
+
int parseopt_column_callback(const struct option *opt,
const char *arg, int unset)
{
diff --git a/column.h b/column.h
index eb03c6c..43528da 100644
--- a/column.h
+++ b/column.h
@@ -27,6 +27,8 @@ extern void print_columns(const struct string_list *list,
struct column_options *opts);
extern int git_config_column(unsigned int *mode, const char *value,
int stdout_is_tty);
+extern int git_column_config(const char *var, const char *value,
+ const char *command, unsigned int *colopts);
struct option;
extern int parseopt_column_callback(const struct option *opt,
--
1.7.8.36.g69ee2
^ permalink raw reply related
* [PATCH v6 08/11] branch: add --column
From: Nguyễn Thái Ngọc Duy @ 2012-02-25 11:41 UTC (permalink / raw)
To: git; +Cc: Junio C Hamano, Nguyễn Thái Ngọc Duy
In-Reply-To: <1330170078-29353-1-git-send-email-pclouds@gmail.com>
Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
Documentation/config.txt | 4 ++
Documentation/git-branch.txt | 9 +++++
Makefile | 2 +-
builtin/branch.c | 32 +++++++++++++++--
t/t3200-branch.sh | 77 ++++++++++++++++++++++++++++++++++++++++++
5 files changed, 119 insertions(+), 5 deletions(-)
diff --git a/Documentation/config.txt b/Documentation/config.txt
index 5216598..c14db27 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -847,6 +847,10 @@ column.ui::
+
This option defaults to 'never'.
+column.branch::
+ Specify whether to output branch listing in `git branch` in columns.
+ See `column.ui` for details.
+
commit.status::
A boolean to enable/disable inclusion of status information in the
commit message template when using an editor to prepare the commit
diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt
index 0427e80..ba5cccb 100644
--- a/Documentation/git-branch.txt
+++ b/Documentation/git-branch.txt
@@ -10,6 +10,7 @@ SYNOPSIS
[verse]
'git branch' [--color[=<when>] | --no-color] [-r | -a]
[--list] [-v [--abbrev=<length> | --no-abbrev]]
+ [--column[=<options>] | --no-column]
[(--merged | --no-merged | --contains) [<commit>]] [<pattern>...]
'git branch' [--set-upstream | --track | --no-track] [-l] [-f] <branchname> [<start-point>]
'git branch' (-m | -M) [<oldbranch>] <newbranch>
@@ -107,6 +108,14 @@ OPTIONS
default to color output.
Same as `--color=never`.
+--column[=<options>]::
+--no-column::
+ Display branch listing in columns. See configuration variable
+ column.branch for option syntax.`--column` and `--no-column`
+ without options are equivalent to 'always' and 'never' respectively.
++
+This option is only applicable in non-verbose mode.
+
-r::
--remotes::
List or delete (if used with -d) the remote-tracking branches.
diff --git a/Makefile b/Makefile
index 0998f0d..320d3f8 100644
--- a/Makefile
+++ b/Makefile
@@ -2168,7 +2168,7 @@ builtin/prune.o builtin/reflog.o reachable.o: reachable.h
builtin/commit.o builtin/revert.o wt-status.o: wt-status.h
builtin/tar-tree.o archive-tar.o: tar.h
connect.o transport.o url.o http-backend.o: url.h
-column.o help.o pager.o: column.h
+builtin/branch.o column.o help.o pager.o: column.h
http-fetch.o http-walker.o remote-curl.o transport.o walker.o: walker.h
http.o http-walker.o http-push.o http-fetch.o remote-curl.o: http.h url.h
diff --git a/builtin/branch.c b/builtin/branch.c
index cb17bc3..611cc0e 100644
--- a/builtin/branch.c
+++ b/builtin/branch.c
@@ -15,6 +15,8 @@
#include "branch.h"
#include "diff.h"
#include "revision.h"
+#include "string-list.h"
+#include "column.h"
static const char * const builtin_branch_usage[] = {
"git branch [options] [-r | -a] [--merged | --no-merged]",
@@ -53,6 +55,9 @@ static enum merge_filter {
} merge_filter;
static unsigned char merge_filter_ref[20];
+static struct string_list output = STRING_LIST_INIT_DUP;
+static unsigned int colopts;
+
static int parse_branch_color_slot(const char *var, int ofs)
{
if (!strcasecmp(var+ofs, "plain"))
@@ -70,6 +75,8 @@ static int parse_branch_color_slot(const char *var, int ofs)
static int git_branch_config(const char *var, const char *value, void *cb)
{
+ if (!prefixcmp(var, "column."))
+ return git_column_config(var, value, "branch", &colopts);
if (!strcmp(var, "color.branch")) {
branch_use_color = git_config_colorbool(var, value);
return 0;
@@ -474,7 +481,12 @@ static void print_ref_item(struct ref_item *item, int maxwidth, int verbose,
else if (verbose)
/* " f7c0c00 [ahead 58, behind 197] vcs-svn: drop obj_pool.h" */
add_verbose_info(&out, item, verbose, abbrev);
- printf("%s\n", out.buf);
+ if (colopts & COL_ENABLED) {
+ assert(!verbose && "--column and --verbose are incompatible");
+ string_list_append(&output, out.buf);
+ } else {
+ printf("%s\n", out.buf);
+ }
strbuf_release(&name);
strbuf_release(&out);
}
@@ -727,6 +739,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
PARSE_OPT_LASTARG_DEFAULT | PARSE_OPT_NONEG,
opt_parse_merge_filter, (intptr_t) "HEAD",
},
+ OPT_COLUMN(0, "column", &colopts, "list branches in columns"),
OPT_END(),
};
@@ -749,6 +762,8 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
}
hashcpy(merge_filter_ref, head_sha1);
+
+ colopts |= COL_ANSI;
argc = parse_options(argc, argv, prefix, options, builtin_branch_usage,
0);
@@ -760,12 +775,21 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
if (abbrev == -1)
abbrev = DEFAULT_ABBREV;
+ if (verbose) {
+ if (explicitly_enable_column(colopts))
+ die(_("--column and --verbose are incompatible"));
+ colopts = 0;
+ }
if (delete)
return delete_branches(argc, argv, delete > 1, kinds);
- else if (list)
- return print_ref_list(kinds, detached, verbose, abbrev,
- with_commit, argv);
+ else if (list) {
+ int ret = print_ref_list(kinds, detached, verbose, abbrev,
+ with_commit, argv);
+ print_columns(&output, colopts, NULL);
+ string_list_clear(&output, 0);
+ return ret;
+ }
else if (edit_description) {
const char *branch_name;
struct strbuf branch_ref = STRBUF_INIT;
diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh
index dd1aceb..c38592a 100755
--- a/t/t3200-branch.sh
+++ b/t/t3200-branch.sh
@@ -160,6 +160,83 @@ test_expect_success 'git branch --list -d t should fail' '
test_path_is_missing .git/refs/heads/t
'
+test_expect_success 'git branch --column' '
+ COLUMNS=80 git branch --column=column >actual &&
+ cat >expected <<\EOF &&
+ a/b/c bam foo l * master n o/p r
+ abc bar j/k m/m master2 o/o q
+EOF
+ test_cmp expected actual
+'
+
+test_expect_success 'git branch --column with an extremely long branch name' '
+ long=this/is/a/part/of/long/branch/name &&
+ long=z$long/$long/$long/$long &&
+ test_when_finished "git branch -d $long" &&
+ git branch $long &&
+ COLUMNS=80 git branch --column=column >actual &&
+ cat >expected <<EOF &&
+ a/b/c
+ abc
+ bam
+ bar
+ foo
+ j/k
+ l
+ m/m
+* master
+ master2
+ n
+ o/o
+ o/p
+ q
+ r
+ $long
+EOF
+ test_cmp expected actual
+'
+
+test_expect_success 'git branch with column.*' '
+ git config column.ui column &&
+ git config column.branch "dense" &&
+ COLUMNS=80 git branch >actual &&
+ git config --unset column.branch &&
+ git config --unset column.ui &&
+ cat >expected <<\EOF &&
+ a/b/c bam foo l * master n o/p r
+ abc bar j/k m/m master2 o/o q
+EOF
+ test_cmp expected actual
+'
+
+test_expect_success 'git branch --column -v should fail' '
+ test_must_fail git branch --column -v
+'
+
+test_expect_success 'git branch -v with column.ui ignored' '
+ git config column.ui column &&
+ COLUMNS=80 git branch -v | cut -c -10 | sed "s/ *$//" >actual &&
+ git config --unset column.ui &&
+ cat >expected <<\EOF &&
+ a/b/c
+ abc
+ bam
+ bar
+ foo
+ j/k
+ l
+ m/m
+* master
+ master2
+ n
+ o/o
+ o/p
+ q
+ r
+EOF
+ test_cmp expected actual
+'
+
mv .git/config .git/config-saved
test_expect_success 'git branch -m q q2 without config should succeed' '
--
1.7.8.36.g69ee2
^ 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