Git development
 help / color / mirror / Atom feed
* [PATCH v2] status: list unmerged files last
From: Johannes Sixt @ 2009-09-01 21:25 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: bill lam, git
In-Reply-To: <7vy6oy9z9r.fsf@alter.siamese.dyndns.org>

The list of unmerged files is considered rather important because after
a conflicted merge they need attention. Since the output of git status does
not go through the pager, the end of the output remains immediately visible
in the terminal window. By placing unmerge entries at the end of the list,
the user can see them immediately.

Moreover, keeping the unmerge entries at the top is inconvenient if a merge
touched many files, but only a few conflicted: After the conflicts were
resolved, the user will conduct a 'git add' command. In order to do that
with copy-and-paste, the user must scroll the terminal window up, and must
do so for each individual entry (because terminal windows commonly scroll
down automatically on the paste operation to make the cursor visible).

Signed-off-by: Johannes Sixt <j6t@kdbg.org>
---
On Dienstag, 1. September 2009, Junio C Hamano wrote:
> Johannes Sixt <j6t@kdbg.org> writes:
> > Moreover, keeping the unmerge entries at the top is inconvenient if a
> > merge touched many files, but only a few conflicted: After the conflicts
> > were resolved, the user will conduct a 'git add' command. In order to do
> > that with copy-and-paste, the user must scroll the terminal window up,
> > and must do so for each individual entry (because terminal windows
> > commonly scroll down automatically on the paste operation to make the
> > cursor visible).
>
> I actually was expecting that you would move this at the very bottom after
> untracked list for the above reason, and also because this part is only
> shown while running status (that was a good point you made in the previous
> message) and never in commit.

So you would not mind a more "drastic" change?

This version 2 can be regarded as a real improvement with the argument
above, whereas version 1 would only correct something of some
sort of regression, compared to v1.6.4.

(Originally I didn't dare to change too much and thought keeping staged
files together would make sense.)

-- Hannes

 wt-status.c |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)

diff --git a/wt-status.c b/wt-status.c
index 3395456..60d8425 100644
--- a/wt-status.c
+++ b/wt-status.c
@@ -561,7 +561,6 @@ void wt_status_print(struct wt_status *s)
 		color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "#");
 	}
 
-	wt_status_print_unmerged(s);
 	wt_status_print_updated(s);
 	wt_status_print_changed(s);
 	if (s->submodule_summary)
@@ -570,6 +569,7 @@ void wt_status_print(struct wt_status *s)
 		wt_status_print_untracked(s);
 	else if (s->commitable)
 		 fprintf(s->fp, "# Untracked files not listed (use -u option to show untracked 
files)\n");
+	wt_status_print_unmerged(s);
 
 	if (s->verbose)
 		wt_status_print_verbose(s);
-- 
1.6.4.2.280.gb16ab

^ permalink raw reply related

* [JGIT PATCH] fixed error in whitespace handling of RefDatabase#readLine
From: Mark Struberg @ 2009-09-01 21:18 UTC (permalink / raw)
  To: Shawn Spearce, robin.rosenberg; +Cc: git

jgit fails with "cannot checkout; no HEAD advertised by remote"
in guessHEAD on some repositories.

JGIT used to work on my test repo for the maven-scm-providers-git until a few weeks. I tracked it down with git bisect and found commit 72b1f0d334729a49cc52e4762093148be62bea39 to be the bad one.

I glimpsed at the code and it appears that the new code in RefDatabase#readLine is not Windows CR+LF aware. (This hits me although I use Linux because our SVN at apache.org seems to have it stored with CR+LF.)

Fixed by subsequently removing all Character.isWhitespaceChar() from the end of the buffer

Signed-off-by: Mark Struberg <struberg@yahoo.de>
---
 .../src/org/spearce/jgit/lib/RefDatabase.java      |    6 +++++-
 1 files changed, 5 insertions(+), 1 deletions(-)

diff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/RefDatabase.java b/org.spearce.jgit/src/org/spearce/jgit/lib/RefDatabase.java
index ba4b654..477dc62 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/lib/RefDatabase.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/RefDatabase.java
@@ -500,8 +500,12 @@ private static String readLine(final File file)
         int n = buf.length;
         if (n == 0)
             return null;
-        if (buf[n - 1] == '\n')
+        
+        // remove trailing whitespaces
+        while (n > 0 && Character.isWhitespace(buf[n - 1])) {
             n--;
+        }
+        
         return RawParseUtils.decode(buf, 0, n);
     }
 
-- 
1.6.2.5


      

^ permalink raw reply related

* [JGIT] potential incompatibility with old git repos.
From: Mark Struberg @ 2009-09-01 21:07 UTC (permalink / raw)
  To: Shawn Spearce, robin.rosenberg; +Cc: git

Hi Shawn, Robin!

I'm happy to spend some time on the maven-scm-providers-jgit again, but spotted a problem with running the latest version of JGIT on my test repository [1]. This test repo has been created a good while ago (~nov 2007) with git 1.3.54 or so and has been edited on various computers.

It works with native git but crashes with both SimpleRepository#clone [2] and jgit.

jgit fails with "cannot checkout; no HEAD advertised by remote"
in guessHEAD

JGIT used to work on this repo until a few weeks. I tracked it down with git bisect and found commit
72b1f0d334729a49cc52e4762093148be62bea39
to be the bad one.

I glimpsed at the code and it appears that the new code in RefDatabase#readLine is not Windows CR+LF aware. (This hits me although I use Linux because our SVN at apache.org seems to have it stored with CR+LF.)

Patch will follow.

txs and LieGrue,
strub


[1] http://github.com/struberg/maven-scm-providers-git/tree/a66e9470ea087030bb8bd40edb2cab22a2de0e6a/maven-scm-provider-gittest/src/main/resources/repository/test-repo
[2] http://github.com/sonatype/JGit/tree/struberg


      

^ permalink raw reply

* Re: [PATCH] status: list unmerged files after staged files
From: Junio C Hamano @ 2009-09-01 20:38 UTC (permalink / raw)
  To: Johannes Sixt; +Cc: Junio C Hamano, bill lam, git
In-Reply-To: <200909012213.54611.j6t@kdbg.org>

Johannes Sixt <j6t@kdbg.org> writes:

> Moreover, keeping the unmerge entries at the top is inconvenient if a merge
> touched many files, but only a few conflicted: After the conflicts were
> resolved, the user will conduct a 'git add' command. In order to do that
> with copy-and-paste, the user must scroll the terminal window up, and must
> do so for each individual entry (because terminal windows commonly scroll
> down automatically on the paste operation to make the cursor visible).

I actually was expecting that you would move this at the very bottom after
untracked list for the above reason, and also because this part is only
shown while running status (that was a good point you made in the previous
message) and never in commit.

>
> Signed-off-by: Johannes Sixt <j6t@kdbg.org>
> ---
>  wt-status.c |    2 +-
>  1 files changed, 1 insertions(+), 1 deletions(-)
>
> diff --git a/wt-status.c b/wt-status.c
> index 3395456..85f3fcb 100644
> --- a/wt-status.c
> +++ b/wt-status.c
> @@ -561,8 +561,8 @@ void wt_status_print(struct wt_status *s)
>  		color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "#");
>  	}
>  
> -	wt_status_print_unmerged(s);
>  	wt_status_print_updated(s);
> +	wt_status_print_unmerged(s);
>  	wt_status_print_changed(s);
>  	if (s->submodule_summary)
>  		wt_status_print_submodule_summary(s);
> -- 
> 1.6.4.2.280.gb16ab

^ permalink raw reply

* Re: [PATCH] Make git status print a helpful death message if the disk is full
From: Thomas Rast @ 2009-09-01 20:35 UTC (permalink / raw)
  To: David Reiss; +Cc: git, Junio C Hamano
In-Reply-To: <4A9D7B54.5020902@facebook.com>

[-- Attachment #1: Type: Text/Plain, Size: 1042 bytes --]

David Reiss wrote:
> The old behavior just said that it failed.  Now it includes the error
> information, which makes it much easier to debug.
> 
> There is a risk that some failure paths could result in misleading error
> messages that actually make debugging more difficult.
[...]
>  		if (write_cache(fd, active_cache, active_nr) ||
>  		    close_lock_file(&index_lock))
> -			die("unable to write new_index file");
> +			die("unable to write new_index file: %s", strerror(errno));

Junio C Hamano wrote:
> Don't we have die_errno() or something since at least 1.6.4?

Yes.  And during the conversion, I ignored call sites like this one
precisely because I did not (and still do not) have enough knowledge
of the index and lock file machinery to decide at what stage I need to
read errno to get the *real* error message.  You're of course welcome
to dig into the code to verify that the above is correct, but I am
against blindly hoping that it gives the right error.

-- 
Thomas Rast
trast@{inf,student}.ethz.ch

[-- Attachment #2: This is a digitally signed message part. --]
[-- Type: application/pgp-signature, Size: 197 bytes --]

^ permalink raw reply

* [PATCH-v2] Make git status print a helpful death message if the disk is full
From: David Reiss @ 2009-09-01 20:27 UTC (permalink / raw)
  To: git

The old behavior just said that it failed.  Now it includes the error
information, which makes it much easier to debug.

There is a risk that some failure paths could result in misleading error
messages that actually make debugging more difficult.

Signed-off-by: David Reiss <dreiss@facebook.com>
---
Sorry I missed that.

 builtin-commit.c |    6 +++---
 1 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/builtin-commit.c b/builtin-commit.c
index 4bcce06..7feb598 100644
--- a/builtin-commit.c
+++ b/builtin-commit.c
@@ -256,7 +256,7 @@ static char *prepare_index(int argc, const char **argv, const char *prefix)
 		refresh_cache(REFRESH_QUIET);
 		if (write_cache(fd, active_cache, active_nr) ||
 		    close_lock_file(&index_lock))
-			die("unable to write new_index file");
+			die_errno("unable to write new_index file");
 		commit_style = COMMIT_NORMAL;
 		return index_lock.filename;
 	}
@@ -275,7 +275,7 @@ static char *prepare_index(int argc, const char **argv, const char *prefix)
 		refresh_cache(REFRESH_QUIET);
 		if (write_cache(fd, active_cache, active_nr) ||
 		    commit_locked_index(&index_lock))
-			die("unable to write new_index file");
+			die_errno("unable to write new_index file");
 		commit_style = COMMIT_AS_IS;
 		return get_index_file();
 	}
@@ -318,7 +318,7 @@ static char *prepare_index(int argc, const char **argv, const char *prefix)
 	refresh_cache(REFRESH_QUIET);
 	if (write_cache(fd, active_cache, active_nr) ||
 	    close_lock_file(&index_lock))
-		die("unable to write new_index file");
+		die_errno("unable to write new_index file");
 
 	fd = hold_lock_file_for_update(&false_lock,
 				       git_path("next-index-%"PRIuMAX,
-- 
1.6.0.4

^ permalink raw reply related

* Re: [PATCH] Make git status print a helpful death message if the disk is full
From: Junio C Hamano @ 2009-09-01 20:19 UTC (permalink / raw)
  To: David Reiss; +Cc: git
In-Reply-To: <4A9D7B54.5020902@facebook.com>

Don't we have die_errno() or something since at least 1.6.4?

^ permalink raw reply

* [PATCH] status: list unmerged files after staged files
From: Johannes Sixt @ 2009-09-01 20:13 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: bill lam, git
In-Reply-To: <200909012140.08953.j6t@kdbg.org>

The list of unmerged files is considered rather important because after
a conflicted merge they need attention. Since the output of git status does
not go through the pager, the end of the output remains immediately visible
in the terminal window. By placing unmerge entries after staged entries,
the user can see them immediately.

Moreover, keeping the unmerge entries at the top is inconvenient if a merge
touched many files, but only a few conflicted: After the conflicts were
resolved, the user will conduct a 'git add' command. In order to do that
with copy-and-paste, the user must scroll the terminal window up, and must
do so for each individual entry (because terminal windows commonly scroll
down automatically on the paste operation to make the cursor visible).

Signed-off-by: Johannes Sixt <j6t@kdbg.org>
---
 wt-status.c |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)

diff --git a/wt-status.c b/wt-status.c
index 3395456..85f3fcb 100644
--- a/wt-status.c
+++ b/wt-status.c
@@ -561,8 +561,8 @@ void wt_status_print(struct wt_status *s)
 		color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "#");
 	}
 
-	wt_status_print_unmerged(s);
 	wt_status_print_updated(s);
+	wt_status_print_unmerged(s);
 	wt_status_print_changed(s);
 	if (s->submodule_summary)
 		wt_status_print_submodule_summary(s);
-- 
1.6.4.2.280.gb16ab

^ permalink raw reply related

* [PATCH] Make git status print a helpful death message if the disk is full
From: David Reiss @ 2009-09-01 19:51 UTC (permalink / raw)
  To: git

The old behavior just said that it failed.  Now it includes the error
information, which makes it much easier to debug.

There is a risk that some failure paths could result in misleading error
messages that actually make debugging more difficult.

Signed-off-by: David Reiss <dreiss@facebook.com>
---
 builtin-commit.c |    6 +++---
 1 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/builtin-commit.c b/builtin-commit.c
index 4bcce06..3527c73 100644
--- a/builtin-commit.c
+++ b/builtin-commit.c
@@ -256,7 +256,7 @@ static char *prepare_index(int argc, const char **argv, const char *prefix)
 		refresh_cache(REFRESH_QUIET);
 		if (write_cache(fd, active_cache, active_nr) ||
 		    close_lock_file(&index_lock))
-			die("unable to write new_index file");
+			die("unable to write new_index file: %s", strerror(errno));
 		commit_style = COMMIT_NORMAL;
 		return index_lock.filename;
 	}
@@ -275,7 +275,7 @@ static char *prepare_index(int argc, const char **argv, const char *prefix)
 		refresh_cache(REFRESH_QUIET);
 		if (write_cache(fd, active_cache, active_nr) ||
 		    commit_locked_index(&index_lock))
-			die("unable to write new_index file");
+			die("unable to write new_index file: %s", strerror(errno));
 		commit_style = COMMIT_AS_IS;
 		return get_index_file();
 	}
@@ -318,7 +318,7 @@ static char *prepare_index(int argc, const char **argv, const char *prefix)
 	refresh_cache(REFRESH_QUIET);
 	if (write_cache(fd, active_cache, active_nr) ||
 	    close_lock_file(&index_lock))
-		die("unable to write new_index file");
+		die("unable to write new_index file: %s", strerror(errno));
 
 	fd = hold_lock_file_for_update(&false_lock,
 				       git_path("next-index-%"PRIuMAX,
-- 
1.6.0.4

^ permalink raw reply related

* Re: unmerged files listed in the beginning of git-status
From: Johannes Sixt @ 2009-09-01 19:40 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: bill lam, git
In-Reply-To: <7vljkypqfi.fsf@alter.siamese.dyndns.org>

On Dienstag, 1. September 2009, Junio C Hamano wrote:
> bill lam <cbill.lam@gmail.com> writes:
> > git-status show unmerged files
> > with a clause of explanation.  This is very helpful. However these
> > unmerged files are listed in the beginning and followed by modified
> > files,
>
> "git status" is preview of what git commit does.  The "Changes to be
> committed" section is given at the beginning of the output because it is
> the most important one.  But while reviewing the conflicts, you would want
> to notice conflicted paths more than what are already resolved and staged.
>
> It used to be that unmerged paths were mixed together with locally
> modified paths in the "Changed but not updated" list, after the "Changes
> to be committed" list.  This made the unmerged paths harder to spot than
> necessary.
>
> To remedy this, unmerged ones are now:
>
>  (1) placed in a new, separate section that appears only when there are
>      unmerged paths, to make the fact that there is something unusual
>      going on (i.e. conflicts) stand out; and
>
>  (2) the new section is given at the top of the status output to give
>      these unmerged paths more prominence.

But this is very inconvenient if you merge a branch that touched many files, 
of which only a few have conflicts. In this case, the unmerged entries are 
scrolled out of view. If you want to copy-paste them into a 'git add' command 
then (at least my) xterm (and Windows's CMD, BTW) keeps scrolling down to the 
command line, and since I cannot bulk-select all of them at once, I have to 
scroll up in order select any individual of them.

Note that I do not complain about the "out of view" part (because if a list is 
long there is inevitably something that becomes invisible), but about 
the "must scroll around" part.

> But unmerged entries are something you need to deal with _first_ before
> being able to go further, so in that sense it is more important than
> anything else in the traditional output.

This is actually an argument to place the unmerged entries *last* because this 
is what will be visible after 'git status' finished. Remember that we don't 
pass its output through the pager.

> In the output, "the most important part first" rule is unlikely to change,
> if only because this is what you are shown when committing in the editor,
> and even in 1.7.0 when "git status" stops being "git commit --dry-run"
> because we would still keep consistency of the two outputs,

Of course, this argument is irrelevant for the placement of the list of 
unmerged entries because by the time you enter the commit message editor, 
this list is empty.

-- Hannes

^ permalink raw reply

* Re: A note from the maintainer: Follow-up questions (MaintNotes)
From: Junio C Hamano @ 2009-09-01 19:09 UTC (permalink / raw)
  To: David Chanters; +Cc: git
In-Reply-To: <ac3d41850909010958l890bf2fyda6e61e3cb082c2a@mail.gmail.com>

David Chanters <david.chanters@googlemail.com> writes:

> 2009/9/1 Junio C Hamano <gitster@pobox.com>:
>>    $ git log --oneline --first-parent origin/master..origin/pu
>>
>> would be a handy way to view where the tip of each branch is.
>
> Yes it is - thanks for that!  I presume that (in other workflows --
> not necessarily git,git's) that using git-resurrect.sh would be
> preferable to the git-log suggestion above if the topic branch wanting
> to be "resurrected" had several merge points?

AFAIK the script is an (attempt of) automating the inspecting of the above
output and manually choosing which one to use.  As automation, it may be
handy, and as automation, it may have bugs ;-).

> Makes sense - and on that note - in our current workflow of using Git,
> we have a feature branch, call it "featureA" which is forked from
> "master" (where our stable code lives) -- but obviously over time, if
> bugfixes happen and get released, what do we do about then ensuring
> that featureA also benefits from these bug-fixes?  Since invariably
> people will want to develop using the bug-fixes, but "featureA" long
> since branched from "master" at a point in the past, before the
> bug-fixes?

That is one of the most common misconception people new to the topic
branch workflow have [*1*].

In general, you do not want to merge fixes or other changes from
integration branches back into a topic branch such as featureA.  The
purpose of a topic branch is to brew the topic and the topic alone in
isolation to perfection.  Once you start merging 'maint' or 'master' into
'featureA', the output from "git log featureA" ceases to be "the work I
did to polish the feature A".  The branch becomes "this contains the work
related to feature A, but overwhelmingly consists of other unrelated
random stuff".  It would be useless as a topic branch, because you cannot
tell which ones are the changes to achieve the goal of the topic anymore.

When you test featureA, you would test the topic branch itself, and while
doing so, you will of course see bugs that have been fixed since you
forked from the mainline.  Do not get distracted by them; you did not fork
the featureA topic in order to fix them in the first place!

Of course, when you test the whole system including the addition of
featureA, you would want to make sure that fixes and enhancements that
happened on the mainline since you forked the topic branch work well with
what you did on the topic.

For that kind of integration testing, either use a temporary branch to
test trial integration:


    $ git checkout -b test master ;# start with everything else they did
    $ git merge featureA ;# ... and try my topic with that
    $ test..test..test

and once you are done, you can get rid of the test branch

    $ git checkout master
    $ git branch -D test

You can alternatively have a dedicated branch for early integration
testing.  That is what 'next' branch is used for in git.git project.

    $ git checkout next
    $ git merge featureA ;# ... merge the update
    $ test..test..test
    ... yeek, my changes were buggy ...
    $ git reset --hard HEAD^ ;# get rid of the change
    $ git checkout featureA
    $ fix..fix..fix
    $ git checkout next
    $ git merge featureA ;# ... try again
    $ test..test..test
    ... this time it is good and I am happy ...


[Footnote]

*1* I think I have had a chance to answer this question in a lot more
detail at least twice in the past couple of weeks on this list, and both
times it was very frustrating for me, not because I did not want to answer
them, but because I've written it in length in my upcoming book but
pointing to it would not help people who asked the question because it is
in Japanese.  I've been writing on a handful of topics I thought were
worthy of general consumption on my blog while writing the book, but this
is one of the topics that I haven't "backported" there yet X-<.

^ permalink raw reply

* Re: Problems with GIT under Windows - "not uptodate"
From: david.hagood @ 2009-09-01 18:25 UTC (permalink / raw)
  To: skillzero; +Cc: david.hagood, git
In-Reply-To: <2729632a0909011119l3a19447ds9d4896a27ac488c1@mail.gmail.com>


> It seems like you want core.autocrlf to be false or you want a
> .gitattributes file to override it on a per-file basis for the files
> you know are CRLF (i.e. the ones that are CRLF even on the Linux
> side).

That's what I've been leaning toward: a .gitattributes in that directory
like this:

*.xml -crlf diff merge

Does that sound reasonable?

^ permalink raw reply

* Re: Problems with GIT under Windows - "not uptodate"
From: skillzero @ 2009-09-01 18:19 UTC (permalink / raw)
  To: david.hagood; +Cc: git
In-Reply-To: <a21e6af7ee05f56fd8c02d0955af1c72.squirrel@localhost>

On Tue, Sep 1, 2009 at 8:46 AM, <david.hagood@gmail.com> wrote:
> I am having a problem trying to support my poor, deluded cow-orkers who
> use Windows and need to use GIT.
>
> The scenario goes something like this:
>
> They have a local repo, they have changes on their branch, they are
> staging a commit to the master branch on their local.
>
> They do a "git merge" and the merge has conflicts. They need to undo the
> merge, so they do a "git reset --hard".
>
> From that point onward, if they try to access the origin repository (e.g.
> "git pull") they get the error message
>
> Error: Entry "Some file name" not uptodate: cannot merge.
>
> We've tried "git reset --hard; git pull ." We've tried "git reset --hard;
> git checkout -f master". Neither seems to fix this.
>
> We Linux users don't see this.
>
> I conjecture it is something to do with DOS's CR/LF line endings (the
> files in question are a type of XML file which ALWAYS have CR/LF endings,
> even under Linux) - perhaps *something* in the GIT processing chain is
> trying to do a CR/LF -> LF conversion, and screwing things up.
>
> Does anybody have any suggestions on what I am doing wrong (Please, not
> "you are using Windows")?

Yeah I run into the same thing on Windows if somebody accidentally
checks in a CRLF file. Do you have core.autocrlf on? If you do, I
think the problem is that it's going to try to convert the line
endings on every checkout/etc, but since the file is already CRLF (not
LF as is normally in the repository), it's going to make the file look
different every time.

It seems like you want core.autocrlf to be false or you want a
.gitattributes file to override it on a per-file basis for the files
you know are CRLF (i.e. the ones that are CRLF even on the Linux
side).

^ permalink raw reply

* Re: A note from the maintainer: Follow-up questions (MaintNotes)
From: David Chanters @ 2009-09-01 16:58 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git
In-Reply-To: <7v8wgzla02.fsf@alter.siamese.dyndns.org>

2009/9/1 Junio C Hamano <gitster@pobox.com>:
>    $ git log --oneline --first-parent origin/master..origin/pu
>
> would be a handy way to view where the tip of each branch is.

Yes it is - thanks for that!  I presume that (in other workflows --
not necessarily git,git's) that using git-resurrect.sh would be
preferable to the git-log suggestion above if the topic branch wanting
to be "resurrected" had several merge points?

> So if you for example happen to be interested in jc/log-tz topic,
> you would do something like:
>
>    $ git checkout -b jc/log-tz 2178d02^2
>    $ git log -p master..
>
> to check out, and view what changes the topic introduces.

This is really useful - thank you - it's solving a missing piece of a
puzzle for me.  :)

> where "ai" is typically the author's initial, and topic-name names the
> topic just like you would name a function.  A topic typically forks from
> the tip of master if it is a new feature, or a much older commit in maint
> if it is a fix (and in such a case, topic-name typically begins with
> a string "maint-").

Makes sense - and on that note - in our current workflow of using Git,
we have a feature branch, call it "featureA" which is forked from
"master" (where our stable code lives) -- but obviously over time, if
bugfixes happen and get released, what do we do about then ensuring
that featureA also benefits from these bug-fixes?  Since invariably
people will want to develop using the bug-fixes, but "featureA" long
since branched from "master" at a point in the past, before the
bug-fixes?

What do you do, about this when handling topic branches merged into
next, or doesn't it really matter by that point?

[...snip really useful explanation...]

I can't thank you enough, Junio for this -- you've effectively ironed
out a workflow here I think I can now go away and start using -
thanks.  :)

David

^ permalink raw reply

* Re: Problems with GIT under Windows - "not uptodate"
From: Eric Raible @ 2009-09-01 16:52 UTC (permalink / raw)
  To: git
In-Reply-To: <a21e6af7ee05f56fd8c02d0955af1c72.squirrel@localhost>

 <david.hagood <at> gmail.com> writes:

> Error: Entry "Some file name" not uptodate: cannot merge.
> 
> We've tried "git reset --hard; git pull ." We've tried "git reset --hard;
> git checkout -f master". Neither seems to fix this.

http://article.gmane.org/gmane.comp.version-control.git/122862

^ permalink raw reply

* Re: What's cooking in git.git (Aug 2009, #06; Sun, 30)
From: Junio C Hamano @ 2009-09-01 16:51 UTC (permalink / raw)
  To: Jakub Narebski; +Cc: git
In-Reply-To: <m37hwili5q.fsf@localhost.localdomain>

Jakub Narebski <jnareb@gmail.com> writes:

> There is replacement series sent to git mailing list a little while
> ago.  

Thanks; I've replaced and pushed them out on 'pu' for now.  Will hopefully
start merging earlier parts to 'next', but how widely is Hires available?

^ permalink raw reply

* Re: What's cooking in git.git (Aug 2009, #06; Sun, 30)
From: Jakub Narebski @ 2009-09-01 16:47 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git
In-Reply-To: <7viqg48nxi.fsf@alter.siamese.dyndns.org>

Junio C Hamano <gitster@pobox.com> writes:

> * jn/gitweb-blame (2009-08-06) 3 commits
>  - gitweb: Create links leading to 'blame_incremental' using JavaScript
>  - gitweb: Incremental blame (WIP)
>  - gitweb: Add optional "time to generate page" info in footer
> 
> Ajax-y blame WIP

There is replacement series sent to git mailing list a little while
ago.  

The replacements for "time to generate page" and 'blame_incremental'
are IMVHO out of WIP (but more testing, in different web browsers
would be good).

The part that actualy creates links that lead to 'blame_incremental'
view is still work in progress, and needs ideas how to correctly
implement it.

-- 
Jakub Narebski
Poland
ShadeHawk on #git

^ permalink raw reply

* Re: unmerged files listed in the beginning of git-status
From: Junio C Hamano @ 2009-09-01 16:42 UTC (permalink / raw)
  To: bill lam; +Cc: git
In-Reply-To: <20090901145213.GB4194@debian.b2j>

bill lam <cbill.lam@gmail.com> writes:

> I noticed in the new git 1.6.4.2 .

I hope you didn't.  This is only in 'master' and will appear first in the
upcoming 1.6.5; it is never meant for 1.6.4.X maintenance series and
1.6.4.2 does not have this change.

> git-status show unmerged files
> with a clause of explanation.  This is very helpful. However these
> unmerged files are listed in the beginning and followed by modified
> files,

"git status" is preview of what git commit does.  The "Changes to be
committed" section is given at the beginning of the output because it is
the most important one.  But while reviewing the conflicts, you would want
to notice conflicted paths more than what are already resolved and staged.

It used to be that unmerged paths were mixed together with locally
modified paths in the "Changed but not updated" list, after the "Changes
to be committed" list.  This made the unmerged paths harder to spot than
necessary.

To remedy this, unmerged ones are now:

 (1) placed in a new, separate section that appears only when there are
     unmerged paths, to make the fact that there is something unusual
     going on (i.e. conflicts) stand out; and

 (2) the new section is given at the top of the status output to give
     these unmerged paths more prominence.

Having said all that, the relative importance of the pieces of information
given in "git status" output is fairly subjective.

If you are a confident, know-what-I-am-doing type, you would see the
"Changes to be committed" list the most important, because that is where
you make sure you have added all the changes you want to include in the
commit.  If you are a forgetful type, on the other hand, you would see
"Changed but not updated" and "Untracked files" more important, because
that is where you make sure there isn't any files you modified and new
files you created that you want to include in the commit but may have
forgotten.  If you are into flipping many branches and often commit your
changes on a wrong branch, you may value the "On branch foo" information
at the top the most.  So in that sense, there cannot be a single right
order of these sections.

But unmerged entries are something you need to deal with _first_ before
being able to go further, so in that sense it is more important than
anything else in the traditional output.

In the output, "the most important part first" rule is unlikely to change,
if only because this is what you are shown when committing in the editor,
and even in 1.7.0 when "git status" stops being "git commit --dry-run"
because we would still keep consistency of the two outputs,

By the way, please do not deflect responses to your message away from
yourself using Mail-Followup-To.

^ permalink raw reply

* Problems with GIT under Windows - "not uptodate"
From: david.hagood @ 2009-09-01 15:46 UTC (permalink / raw)
  To: git

I am having a problem trying to support my poor, deluded cow-orkers who
use Windows and need to use GIT.

The scenario goes something like this:

They have a local repo, they have changes on their branch, they are
staging a commit to the master branch on their local.

They do a "git merge" and the merge has conflicts. They need to undo the
merge, so they do a "git reset --hard".

>From that point onward, if they try to access the origin repository (e.g.
"git pull") they get the error message

Error: Entry "Some file name" not uptodate: cannot merge.

We've tried "git reset --hard; git pull ." We've tried "git reset --hard;
git checkout -f master". Neither seems to fix this.

We Linux users don't see this.

I conjecture it is something to do with DOS's CR/LF line endings (the
files in question are a type of XML file which ALWAYS have CR/LF endings,
even under Linux) - perhaps *something* in the GIT processing chain is
trying to do a CR/LF -> LF conversion, and screwing things up.

Does anybody have any suggestions on what I am doing wrong (Please, not
"you are using Windows")?

^ permalink raw reply

* Re: What's cooking in git.git (Aug 2009, #06; Sun, 30)
From: Peter Krefting @ 2009-09-01 15:08 UTC (permalink / raw)
  Cc: Git Mailing List
In-Reply-To: <7viqg48nxi.fsf@alter.siamese.dyndns.org>

Junio C Hamano:

> * pk/import-dirs (2009-08-24) 1 commit
> - Add script for importing bits-and-pieces to Git.
>
> This version makes me suspect that the author might regret the choice of 
> the import format that does not allow escaping of paths, nor does not 
> allow leading blanks for readability without changing semantics, both of 
> which make it somewhat limiting and error prone.  These issues will be 
> hard to rectify without breaking the backward compatibility, for a tool 
> that could otherwise turn out to be useful.

If anyone has suggestions on improvements that can help with these issues, 
feel free to submit additional patches. Backwards compatibility is not an 
issue at the moment since it is new material and I so far is the only user 
of the tool (and since it is meant for one-shot imports, backwards 
compatibility is not very important).

-- 
\\// Peter - http://www.softwolves.pp.se/

^ permalink raw reply

* unmerged files listed in the beginning of git-status
From: bill lam @ 2009-09-01 14:52 UTC (permalink / raw)
  To: git

I noticed in the new git 1.6.4.2 . git-status show unmerged files
with a clause of explanation.  This is very helpful. However these
unmerged files are listed in the beginning and followed by modified
files,  I imagined the normal case will be there is only a few
unmerged files but a much large number of other files.  Thus it needs
to shift pageup or redirect to less in order view these unmerges
files.  Previously these unmerge files are listed after modified files
and easily seen or copy-and-paste using mouse.  Is there any specific
reason to change the order of sequence?

-- 
regards,
====================================================
GPG key 1024D/4434BAB3 2008-08-24
gpg --keyserver subkeys.pgp.net --recv-keys 4434BAB3

^ permalink raw reply

* Re: Making GIT XML aware?
From: Avery Pennarun @ 2009-09-01 14:06 UTC (permalink / raw)
  To: david.hagood; +Cc: git
In-Reply-To: <fcdc4d8bb3b1ee0b1473a48ed79e7c61.squirrel@localhost>

On Tue, Sep 1, 2009 at 1:57 PM, <david.hagood@gmail.com> wrote:
> However, it seems to me that if there were some way to plug into GIT's
> merging logic, it would be possible to design an XML aware merging tool
> that might help on this (generalizing: if you could have content-aware
> merging libraries you could make all sorts of merges go more smoothly).
> For the specific case of an XML file, if you could have some way to denote
> tags and/or attributes that are "don't cares" you could address problems
> like I am having. You could also theoretically exploit a knowledge of the
> format to better identify what chunks are changes and possibly track
> motion within the files better.

You have a couple of options here, both of which you can read about in
'man gitattributes'.

If you have "don't care" attributes, that's a good sign that you
shouldn't be storing them *at all*.  You could use the 'filter'
feature of gitattributes to remove them (or convert them to a
constant) on checkin, and regenerate them on checkout.

As for merging algorithms, you can supply a custom one using the
'merge' gitattribute.

> Absent that, is there a way to tell git "in case of an unresolvable merge
> conflict, don't modify the file but put the other version of the file
> somewhere (e.g. filename.other) so that I can use an external tool to
> resolve the differences"? In this case, EA doesn't know how to use the
> standard conflict tags within a file to extract deltas.

You can apparently override this with the 'merge' attribute as well.

Have fun,

Avery

^ permalink raw reply

* Re: Making GIT XML aware?
From: Jakub Narebski @ 2009-09-01 14:25 UTC (permalink / raw)
  To: david.hagood; +Cc: git
In-Reply-To: <fcdc4d8bb3b1ee0b1473a48ed79e7c61.squirrel@localhost>

david.hagood@gmail.com writes:

> I have a project that is committing several XML files into GIT, and we
> have a problem when doing merges.
> 
> The files are UML XMI 1.1 files generated by a UML tool (specifically
> Enterprise Architect by Sparx), and EA "helpfully" puts things like
> timestamps of modification and access into the files. As you can guess,
> these are conflict-magnets. Yes, the ideal solution would be to turn that
> off, and I am pursuing that avenue within EA.
> 
> However, it seems to me that if there were some way to plug into GIT's
> merging logic, it would be possible to design an XML aware merging tool
> that might help on this (generalizing: if you could have content-aware
> merging libraries you could make all sorts of merges go more smoothly).
> For the specific case of an XML file, if you could have some way to denote
> tags and/or attributes that are "don't cares" you could address problems
> like I am having. You could also theoretically exploit a knowledge of the
> format to better identify what chunks are changes and possibly track
> motion within the files better.

With Git you are able to use custom merge driver for specific files
(files specified by glob, which can be all files) by using `merge`
gitattribute (it is about file-level merging in the case of file
contents conflict).  Or you can use 'merge.default' config variable to
set merge driver for all files.

Also you can set `diff` filter to custom diff driver which ignores
timestamps, or even remove timestamps from files when checking them in
using `filter` gitattribute (clean / smudge filters).

> Absent that, is there a way to tell git "in case of an unresolvable merge
> conflict, don't modify the file but put the other version of the file
> somewhere (e.g. filename.other) so that I can use an external tool to
> resolve the differences"? In this case, EA doesn't know how to use the
> standard conflict tags within a file to extract deltas.
 
You can have `merge` attribute unset , e.g 

  *.uml  -merge

This means "Take the version from the current branch as the tentative
merge result, and declare that the merge has conflicts."


Also if you have some graphical merge tool, you can consider
configuring and using git-mergetool.

-- 
Jakub Narebski
Poland
ShadeHawk on #git

^ permalink raw reply

* Making GIT XML aware?
From: david.hagood @ 2009-09-01 13:57 UTC (permalink / raw)
  To: git

I have a project that is committing several XML files into GIT, and we
have a problem when doing merges.

The files are UML XMI 1.1 files generated by a UML tool (specifically
Enterprise Architect by Sparx), and EA "helpfully" puts things like
timestamps of modification and access into the files. As you can guess,
these are conflict-magnets. Yes, the ideal solution would be to turn that
off, and I am pursuing that avenue within EA.

However, it seems to me that if there were some way to plug into GIT's
merging logic, it would be possible to design an XML aware merging tool
that might help on this (generalizing: if you could have content-aware
merging libraries you could make all sorts of merges go more smoothly).
For the specific case of an XML file, if you could have some way to denote
tags and/or attributes that are "don't cares" you could address problems
like I am having. You could also theoretically exploit a knowledge of the
format to better identify what chunks are changes and possibly track
motion within the files better.

Absent that, is there a way to tell git "in case of an unresolvable merge
conflict, don't modify the file but put the other version of the file
somewhere (e.g. filename.other) so that I can use an external tool to
resolve the differences"? In this case, EA doesn't know how to use the
standard conflict tags within a file to extract deltas.

^ permalink raw reply

* [PATCHv5 2/5] gitweb: Incremental blame (using JavaScript)
From: Jakub Narebski @ 2009-09-01 11:39 UTC (permalink / raw)
  To: git
  Cc: Petr Baudis, Fredrik Kuivinen, Giuseppe Bilotta, Luben Tuikov,
	Martin Koegler, Jakub Narebski
In-Reply-To: <1251805160-5303-2-git-send-email-jnareb@gmail.com>

Add 'blame_incremental' view, which uses "git blame --incremental"
and JavaScript (Ajax), where 'blame' use "git blame --porcelain".

* gitweb generates initial info by putting file contents (from
  "git cat-file") together with line numbers in blame table
* then gitweb makes web browser JavaScript engine call startBlame()
  function from gitweb.js
* startBlame() opens XMLHttpRequest connection to 'blame_data' view,
  which in turn calls "git blame --incremental" for a file, and
  streams output of git-blame to JavaScript (gitweb.js)
* XMLHttpRequest event handler updates line info in blame view as soon
  as it gets data from 'blame_data' (from server), and it also updates
  progress info
* when 'blame_data' ends, and gitweb.js finishes updating line info,
  it fixes colors to match (as far as possible) ordinary 'blame' view,
  and updates information about how long it took to generate page.

Gitweb deals with streamed 'blame_data' server errors by displaying
them in the progress info area (just in case).

The 'blame_incremental' view tries to be equivalent to 'blame' action;
there are however a few differences in output between 'blame' and
'blame_incremental' view:
* 'blame_incremental' always used query form for this part of link(s)
  which is generated by JavaScript code.  The difference is visible
  if we use path_info link (pass some or all arguments in path_info).
  Changing this would require implementing something akin to href()
  subroutine from gitweb.perl in JavaScript (in gitweb.js).
* 'blame_incremental' always uses "rowspan" attribute, even if
  rowspan="1".  This simplifies code, and is not visible to user.
* The progress bar and progress info are still there even after
  JavaScript part of 'blame_incremental' finishes work.

Note that currently no link generated by gitweb leads to this new
view.


This code is based on patch by Petr Baudis <pasky@suse.cz> patch, which
in turn was tweaked up version of Fredrik Kuivinen <frekui@gmail.com>'s
proof of concept patch.

This patch adds GITWEB_JS compile configuration option, and modifies
git-instaweb.sh to take gitweb.js into account.  The code for
git-instaweb.sh was taken from Pasky's patch.

Signed-off-by: Fredrik Kuivinen <frekui@gmail.com>
Signed-off-by: Petr Baudis <pasky@suse.cz>
Signed-off-by: Jakub Narebski <jnareb@gmail.com>
---
References:
1. Original patch by Frederik Kuivinen
   http://article.gmane.org/gmane.comp.version-control.git/41361
2. Tweaked up version by Petr Baudis
   http://article.gmane.org/gmane.comp.version-control.git/47614
   http://article.gmane.org/gmane.comp.version-control.git/56657
3. New link rewriting and some optimization in Matrin Koegler
   series introducing some JavaScript support in Git
   http://thread.gmane.org/gmane.comp.version-control.git/47902/focus=47905   
4. My earlier patches
   http://thread.gmane.org/gmane.comp.version-control.git/102657/focus=102712
   http://article.gmane.org/gmane.comp.version-control.git/123202
   http://thread.gmane.org/gmane.comp.version-control.git/123957/focus=123968
   http://thread.gmane.org/gmane.comp.version-control.git/125096/focus=125717

Changes compared to last version (v4):
* The file with JavaScript code for 'blame_incremental' got renamed
  from 'blame.js' to 'gitweb.js' -- it is now meant to contain all
  JavaScript code, as per Google and Yahoo! guidelines (there should
  be one file with JavaScript code, to be cached, as JavaScript
  loading is blocking).
* Move coloring rows duting 'blame_data' run to a separate commit.
* The debug(str) function and its use (even commented out) was removed;
  the code is now meant to be in production, and should have leftover
  debugging statements.
* spacePad(input, width) function, which pads with '&nbsp;' got
  replaced by more generic padLeftStr(input, width, padstr) function.
* Added information about global variables used by function via 
  @globals annotation (non in JSDoc standard).
* Make prevDataLength and nextReadPos properties (fields) of
  XMLHttpRequest object, instead of being global variables.
* Pass xhr as parameter to handleError and responseLoaded functions
  (it shadows xhr as a global variable).
* Move setting onreadystatechange handler before xhr.open()
* Add information about GITWEB_JS Makefile configuration variable to
  gitweb/README
* Remove unnecessary 'reminder' comments in gitweb.js

TODO for future commits:
* Use W3C Progress Events (progress, error, load) in addition to
  currently used readystatechange event
    http://www.w3.org/TR/progress-events/
    http://www.w3.org/TR/XMLHttpRequest2/
    http://www.nczonline.net/blog/2009/07/09/firefox-35firebug-xmlhttprequest-and-readystatechange-bug/
  if XMLHttpResponse supports it.
* handleResponse is used both as onreadystatechange and pollTimer;
  if onreadystatechange works for partial responses we can turn off
  the timer.
* JavaScript is single theraded, therefore there is no need for
  critical section; remove inProgress global variable (flag), and
  busy-read while loop.
* Remove (fade out) progress bar and progress info after blame
  incremental finished run, if possible with large amount of code to
  deal with browser incompatibilities.

TODO and possible extensions:
* Profile gitweb.js using YUI Profiler (or other JavaScript profile tool)
    http://developer.yahoo.com/yui/profiler/
  to check whether performance improvements described in articles
  on NCZOnline blog by Nicholas C. Zakas are required, and would help
    http://www.nczonline.net/blog/
* Get rid of global variables, if possible.
* Instead of running startBlame, put it in window.onload handler.

Roads not taken (perhaps that should be part of commit message?):
* Move most (or all) of "git blame --incremental" output parsing to
  server side, and instead of sending direct output in text/plain,
  send processed data in JSON format, e.g.

    {"commit": {
       "sha1": "e83c5163316f89bfbde7d9ab23ca2e25604af290",
       "info": "Kay Sievers, 2005-08-07 21:49:46 +0200",
       "author-initials": "KS",
       ...
     },
     "src-line": 13,
     "dst-line": 16,
     "numlines": 3,
     "filename": "README"
     }

  (line wrapping added for readibility).  This would require however
  taking care on Perl side to send properly formatted JSON, and on
  JavaScript side including json2.js code to read JSON in gitweb.js
  (unless we rely on eval).
* Using some lightweight JavaScript library (framework), like jQuery,
  Prototype, ExtJS, MooTools, etc.  One one hand side this means not
  having to worry about browser incompatibilities as this would be
  taken care of by library; on the other hand side we want gitweb to
  have as few dependences as possible.
* Use 'multipart/x-mixed-replace': first it is not standard, second
  I don't think this would be a good solution for our problem.

 Makefile           |    6 +-
 git-instaweb.sh    |    7 +
 gitweb/README      |    4 +
 gitweb/gitweb.css  |   11 +
 gitweb/gitweb.js   |  726 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 gitweb/gitweb.perl |  272 ++++++++++++++------
 6 files changed, 939 insertions(+), 87 deletions(-)
 create mode 100644 gitweb/gitweb.js

diff --git a/Makefile b/Makefile
index a614347..407b35c 100644
--- a/Makefile
+++ b/Makefile
@@ -261,6 +261,7 @@ GITWEB_HOMETEXT = indextext.html
 GITWEB_CSS = gitweb.css
 GITWEB_LOGO = git-logo.png
 GITWEB_FAVICON = git-favicon.png
+GITWEB_JS = gitweb.js
 GITWEB_SITE_HEADER =
 GITWEB_SITE_FOOTER =
 
@@ -1393,13 +1394,14 @@ gitweb/gitweb.cgi: gitweb/gitweb.perl
 	    -e 's|++GITWEB_CSS++|$(GITWEB_CSS)|g' \
 	    -e 's|++GITWEB_LOGO++|$(GITWEB_LOGO)|g' \
 	    -e 's|++GITWEB_FAVICON++|$(GITWEB_FAVICON)|g' \
+	    -e 's|++GITWEB_JS++|$(GITWEB_JS)|g' \
 	    -e 's|++GITWEB_SITE_HEADER++|$(GITWEB_SITE_HEADER)|g' \
 	    -e 's|++GITWEB_SITE_FOOTER++|$(GITWEB_SITE_FOOTER)|g' \
 	    $< >$@+ && \
 	chmod +x $@+ && \
 	mv $@+ $@
 
-git-instaweb: git-instaweb.sh gitweb/gitweb.cgi gitweb/gitweb.css
+git-instaweb: git-instaweb.sh gitweb/gitweb.cgi gitweb/gitweb.css gitweb/gitweb.js
 	$(QUIET_GEN)$(RM) $@ $@+ && \
 	sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \
 	    -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
@@ -1408,6 +1410,8 @@ git-instaweb: git-instaweb.sh gitweb/gitweb.cgi gitweb/gitweb.css
 	    -e '/@@GITWEB_CGI@@/d' \
 	    -e '/@@GITWEB_CSS@@/r gitweb/gitweb.css' \
 	    -e '/@@GITWEB_CSS@@/d' \
+	    -e '/@@GITWEB_JS@@/r gitweb/gitweb.js' \
+	    -e '/@@GITWEB_JS@@/d' \
 	    -e 's|@@PERL@@|$(PERL_PATH_SQ)|g' \
 	    $@.sh > $@+ && \
 	chmod +x $@+ && \
diff --git a/git-instaweb.sh b/git-instaweb.sh
index d96eddb..701c0d7 100755
--- a/git-instaweb.sh
+++ b/git-instaweb.sh
@@ -375,8 +375,15 @@ gitweb_css () {
 EOFGITWEB
 }
 
+gitweb_js () {
+	cat > "$1" <<\EOFGITWEB
+@@GITWEB_JS@@
+EOFGITWEB
+}
+
 gitweb_cgi "$GIT_DIR/gitweb/gitweb.cgi"
 gitweb_css "$GIT_DIR/gitweb/gitweb.css"
+gitweb_js  "$GIT_DIR/gitweb/gitweb.js"
 
 case "$httpd" in
 *lighttpd*)
diff --git a/gitweb/README b/gitweb/README
index 66c6a93..b69b0e5 100644
--- a/gitweb/README
+++ b/gitweb/README
@@ -92,6 +92,10 @@ You can specify the following configuration variables when building GIT:
    web browsers that support favicons (website icons) may display them
    in the browser's URL bar and next to site name in bookmarks).  Relative
    to base URI of gitweb.  [Default: git-favicon.png]
+ * GITWEB_JS
+   Points to the localtion where you put gitweb.js on your web server
+   (or to be more generic URI of JavaScript code used by gitweb).
+   Relative to base URI of gitweb.  [Default: gitweb.js]
  * GITWEB_CONFIG
    This Perl file will be loaded using 'do' and can be used to override any
    of the options above as well as some other options -- see the "Runtime
diff --git a/gitweb/gitweb.css b/gitweb/gitweb.css
index 00e2e4c..69ef119 100644
--- a/gitweb/gitweb.css
+++ b/gitweb/gitweb.css
@@ -353,6 +353,17 @@ td.mode {
 	font-family: monospace;
 }
 
+/* progress of blame_interactive */
+div#progress_bar {
+	height: 2px;
+	margin-bottom: -2px;
+	background-color: #d8d9d0;
+}
+div#progress_info {
+	float: right;
+	text-align: right;
+}
+
 /* styling of diffs (patchsets): commitdiff and blobdiff views */
 div.diff.header,
 div.diff.extended_header {
diff --git a/gitweb/gitweb.js b/gitweb/gitweb.js
new file mode 100644
index 0000000..c8411e7
--- /dev/null
+++ b/gitweb/gitweb.js
@@ -0,0 +1,726 @@
+// Copyright (C) 2007, Fredrik Kuivinen <frekui@gmail.com>
+//               2007, Petr Baudis <pasky@suse.cz>
+//          2008-2009, Jakub Narebski <jnareb@gmail.com>
+
+/**
+ * @fileOverview JavaScript code for gitweb (git web interface).
+ * @license GPLv2 or later
+ */
+
+/*
+ * This code uses DOM methods instead of (nonstandard) innerHTML
+ * to modify page.
+ *
+ * innerHTML is non-standard IE extension, though supported by most
+ * browsers; however Firefox up to version 1.5 didn't implement it in
+ * a strict mode (application/xml+xhtml mimetype).
+ *
+ * Also my simple benchmarks show that using elem.firstChild.data =
+ * 'content' is slightly faster than elem.innerHTML = 'content'.  It
+ * is however more fragile (text element fragment must exists), and
+ * less feature-rich (we cannot add HTML).
+ *
+ * Note that DOM 2 HTML is preferred over generic DOM 2 Core; the
+ * equivalent using DOM 2 Core is usually shown in comments.
+ */
+
+
+/* ============================================================ */
+/* generic utility functions */
+
+
+/**
+ * pad number N with nonbreakable spaces on the left, to WIDTH characters
+ * example: padLeftStr(12, 3, '&nbsp;') == '&nbsp;12'
+ *          ('&nbsp;' is nonbreakable space)
+ *
+ * @param {Number|String} input: number to pad
+ * @param {Number} width: visible width of output
+ * @param {String} str: string to prefix to string, e.g. '&nbsp;'
+ * @returns {String} INPUT prefixed with (WIDTH - INPUT.length) x STR
+ */
+function padLeftStr(input, width, str) {
+	var prefix = '';
+
+	width -= input.toString().length;
+	while (width > 1) {
+		prefix += str;
+		width--;
+	}
+	return prefix + input;
+}
+
+/**
+ * Pad INPUT on the left to SIZE width, using given padding character CH,
+ * for example padLeft('a', 3, '_') is '__a'.
+ *
+ * @param {String} input: input value converted to string.
+ * @param {Number} width: desired length of output.
+ * @param {String} ch: single character to prefix to string.
+ *
+ * @returns {String} Modified string, at least SIZE length.
+ */
+function padLeft(input, width, ch) {
+	var s = input + "";
+	while (s.length < width) {
+		s = ch + s;
+	}
+	return s;
+}
+
+/**
+ * Create XMLHttpRequest object in cross-browser way
+ * @returns XMLHttpRequest object, or null
+ */
+function createRequestObject() {
+	try {
+		return new XMLHttpRequest();
+	} catch (e) {}
+	try {
+		return window.createRequest();
+	} catch (e) {}
+	try {
+		return new ActiveXObject("Msxml2.XMLHTTP");
+	} catch (e) {}
+	try {
+		return new ActiveXObject("Microsoft.XMLHTTP");
+	} catch (e) {}
+
+	return null;
+}
+
+/* ============================================================ */
+/* utility/helper functions (and variables) */
+
+var xhr;        // XMLHttpRequest object
+var projectUrl; // partial query + separator ('?' or ';')
+
+// 'commits' is an associative map. It maps SHA1s to Commit objects.
+var commits = {};
+
+/**
+ * constructor for Commit objects, used in 'blame'
+ * @class Represents a blamed commit
+ * @param {String} sha1: SHA-1 identifier of a commit
+ */
+function Commit(sha1) {
+	if (this instanceof Commit) {
+		this.sha1 = sha1;
+		this.nprevious = 0; /* number of 'previous', effective parents */
+	} else {
+		return new Commit(sha1);
+	}
+}
+
+/* ............................................................ */
+/* progress info, timing, error reporting */
+
+var blamedLines = 0;
+var totalLines  = '???';
+var div_progress_bar;
+var div_progress_info;
+
+/**
+ * Detects how many lines does a blamed file have,
+ * This information is used in progress info
+ *
+ * @returns {Number|String} Number of lines in file, or string '...'
+ */
+function countLines() {
+	var table =
+		document.getElementById('blame_table') ||
+		document.getElementsByTagName('table')[0];
+
+	if (table) {
+		return table.getElementsByTagName('tr').length - 1; // for header
+	} else {
+		return '...';
+	}
+}
+
+/**
+ * update progress info and length (width) of progress bar
+ *
+ * @globals div_progress_info, div_progress_bar, blamedLines, totalLines
+ */
+function updateProgressInfo() {
+	if (!div_progress_info) {
+		div_progress_info = document.getElementById('progress_info');
+	}
+	if (!div_progress_bar) {
+		div_progress_bar = document.getElementById('progress_bar');
+	}
+	if (!div_progress_info && !div_progress_bar) {
+		return;
+	}
+
+	var percentage = Math.floor(100.0*blamedLines/totalLines);
+
+	if (div_progress_info) {
+		div_progress_info.firstChild.data  = blamedLines + ' / ' + totalLines +
+			' (' + padLeftStr(percentage, 3, '&nbsp;') + '%)';
+	}
+
+	if (div_progress_bar) {
+		//div_progress_bar.setAttribute('style', 'width: '+percentage+'%;');
+		div_progress_bar.style.width = percentage + '%';
+	}
+}
+
+
+var t_interval_server = '';
+var cmds_server = '';
+var t0 = new Date();
+
+/**
+ * write how much it took to generate data, and to run script
+ *
+ * @globals t0, t_interval_server, cmds_server
+ */
+function writeTimeInterval() {
+	var info_time = document.getElementById('generating_time');
+	if (!info_time || !t_interval_server) {
+		return;
+	}
+	var t1 = new Date();
+	info_time.firstChild.data += ' + (' +
+		t_interval_server + ' sec server blame_data / ' +
+		(t1.getTime() - t0.getTime())/1000 + ' sec client JavaScript)';
+
+	var info_cmds = document.getElementById('generating_cmd');
+	if (!info_time || !cmds_server) {
+		return;
+	}
+	info_cmds.firstChild.data += ' + ' + cmds_server;
+}
+
+/**
+ * show an error message alert to user within page (in prohress info area)
+ * @param {String} str: plain text error message (no HTML)
+ *
+ * @globals div_progress_info
+ */
+function errorInfo(str) {
+	if (!div_progress_info) {
+		div_progress_info = document.getElementById('progress_info');
+	}
+	if (div_progress_info) {
+		div_progress_info.className = 'error';
+		div_progress_info.firstChild.data = str;
+	}
+}
+
+/* ............................................................ */
+/* coloring rows like 'blame' after 'blame_data' finishes */
+
+/**
+ * returns true if given row element (tr) is first in commit group
+ * to be used only after 'blame_data' finishes (after processing)
+ *
+ * @param {HTMLElement} tr: table row
+ * @returns {Boolean} true if TR is first in commit group
+ */
+function isStartOfGroup(tr) {
+	return tr.firstChild.className === 'sha1';
+}
+
+var colorRe = /(?:light|dark)/;
+
+/**
+ * change colors to use zebra coloring (2 colors) instead of 3 colors
+ * concatenate neighbour commit groups belonging to the same commit
+ *
+ * @globals colorRe
+ */
+function fixColorsAndGroups() {
+	var colorClasses = ['light', 'dark'];
+	var linenum = 1;
+	var tr, prev_group;
+	var colorClass = 0;
+	var table =
+		document.getElementById('blame_table') ||
+		document.getElementsByTagName('table')[0];
+
+	while ((tr = document.getElementById('l'+linenum))) {
+	// index origin is 0, which is table header; start from 1
+	//while ((tr = table.rows[linenum])) { // <- it is slower
+		if (isStartOfGroup(tr, linenum, document)) {
+			if (prev_group &&
+			    prev_group.firstChild.firstChild.href ===
+			            tr.firstChild.firstChild.href) {
+				// we have to concatenate groups
+				var prev_rows = prev_group.firstChild.rowSpan || 1;
+				var curr_rows =         tr.firstChild.rowSpan || 1;
+				prev_group.firstChild.rowSpan = prev_rows + curr_rows;
+				//tr.removeChild(tr.firstChild);
+				tr.deleteCell(0); // DOM2 HTML way
+			} else {
+				colorClass = (colorClass + 1) % 2;
+				prev_group = tr;
+			}
+		}
+		var tr_class = tr.className;
+		tr.className = tr_class.replace(colorRe, colorClasses[colorClass]);
+		linenum++;
+	}
+}
+
+/* ............................................................ */
+/* time and data */
+
+/**
+ * used to extract hours and minutes from timezone info, e.g '-0900'
+ * @constant
+ */
+var tzRe = /^([+-][0-9][0-9])([0-9][0-9])$/;
+
+/**
+ * return date in local time formatted in iso-8601 like format
+ * 'yyyy-mm-dd HH:MM:SS +/-ZZZZ' e.g. '2005-08-07 21:49:46 +0200'
+ *
+ * @param {Number} epoch: seconds since '00:00:00 1970-01-01 UTC'
+ * @param {String} timezoneInfo: numeric timezone '(+|-)HHMM'
+ * @returns {String} date in local time in iso-8601 like format
+ *
+ * @globals tzRe
+ */
+function formatDateISOLocal(epoch, timezoneInfo) {
+	var match = tzRe.exec(timezoneInfo);
+	// date corrected by timezone
+	var localDate = new Date(1000 * (epoch +
+		(parseInt(match[1],10)*3600 + parseInt(match[2],10)*60)));
+	var localDateStr = // e.g. '2005-08-07'
+		localDate.getUTCFullYear()                 + '-' +
+		padLeft(localDate.getUTCMonth()+1, 2, '0') + '-' +
+		padLeft(localDate.getUTCDate(),    2, '0');
+	var localTimeStr = // e.g. '21:49:46'
+		padLeft(localDate.getUTCHours(),   2, '0') + ':' +
+		padLeft(localDate.getUTCMinutes(), 2, '0') + ':' +
+		padLeft(localDate.getUTCSeconds(), 2, '0');
+
+	return localDateStr + ' ' + localTimeStr + ' ' + timezoneInfo;
+}
+
+/* ............................................................ */
+/* unquoting/unescaping filenames */
+
+/**#@+
+ * @constant
+ */
+var escCodeRe = /\\([^0-7]|[0-7]{1,3})/g;
+var octEscRe = /^[0-7]{1,3}$/;
+var maybeQuotedRe = /^\"(.*)\"$/;
+/**#@-*/
+
+/**
+ * unquote maybe git-quoted filename
+ * e.g. 'aa' -> 'aa', '"a\ta"' -> 'a	a'
+ *
+ * @param {String} str: git-quoted string
+ * @returns {String} Unquoted and unescaped string
+ *
+ * @globals escCodeRe, octEscRe, maybeQuotedRe
+ */
+function unquote(str) {
+	function unq(seq) {
+		var es = {
+			// character escape codes, aka escape sequences (from C)
+			// replacements are to some extent JavaScript specific
+			t: "\t",   // tab            (HT, TAB)
+			n: "\n",   // newline        (NL)
+			r: "\r",   // return         (CR)
+			f: "\f",   // form feed      (FF)
+			b: "\b",   // backspace      (BS)
+			a: "\x07", // alarm (bell)   (BEL)
+			e: "\x1B", // escape         (ESC)
+			v: "\v"    // vertical tab   (VT)
+		};
+
+		if (seq.search(octEscRe) !== -1) {
+			// octal char sequence
+			return String.fromCharCode(parseInt(seq, 8));
+		} else if (seq in es) {
+			// C escape sequence, aka character escape code
+			return es[seq];
+		}
+		// quoted ordinary character
+		return seq;
+	}
+
+	var match = str.match(maybeQuotedRe);
+	if (match) {
+		str = match[1];
+		// perhaps str = eval('"'+str+'"'); would be enough?
+		str = str.replace(escCodeRe,
+			function (substr, p1, offset, s) { return unq(p1); });
+	}
+	return str;
+}
+
+/* ============================================================ */
+/* main part: parsing response */
+
+/**
+ * Function called for each blame entry, as soon as it finishes.
+ * It updates page via DOM manipulation, adding sha1 info, etc.
+ *
+ * @param {Commit} commit: blamed commit
+ * @param {Object} group: object representing group of lines,
+ *                        which blame the same commit (blame entry)
+ *
+ * @globals blamedLines
+ */
+function handleLine(commit, group) {
+	/* 
+	   This is the structure of the HTML fragment we are working
+	   with:
+
+	   <tr id="l123" class="">
+	     <td class="sha1" title=""><a href=""> </a></td>
+	     <td class="linenr"><a class="linenr" href="">123</a></td>
+	     <td class="pre"># times (my ext3 doesn&#39;t).</td>
+	   </tr>
+	*/
+
+	var resline = group.resline;
+
+	// format date and time string only once per commit
+	if (!commit.info) {
+		/* e.g. 'Kay Sievers, 2005-08-07 21:49:46 +0200' */
+		commit.info = commit.author + ', ' +
+			formatDateISOLocal(commit.authorTime, commit.authorTimezone);
+	}
+
+	// loop over lines in commit group
+	for (var i = 0; i < group.numlines; i++, resline++) {
+		var tr = document.getElementById('l'+resline);
+		if (!tr) {
+			break;
+		}
+		/*
+			<tr id="l123" class="">
+			  <td class="sha1" title=""><a href=""> </a></td>
+			  <td class="linenr"><a class="linenr" href="">123</a></td>
+			  <td class="pre"># times (my ext3 doesn&#39;t).</td>
+			</tr>
+		*/
+		var td_sha1  = tr.firstChild;
+		var a_sha1   = td_sha1.firstChild;
+		var a_linenr = td_sha1.nextSibling.firstChild;
+
+		/* <tr id="l123" class=""> */
+		var tr_class = 'light'; // or tr.className
+		if (commit.boundary) {
+			tr_class += ' boundary';
+		}
+		if (commit.nprevious === 0) {
+			tr_class += ' no-previous';
+		} else if (commit.nprevious > 1) {
+			tr_class += ' multiple-previous';
+		}
+		tr.className = tr_class;
+
+		/* <td class="sha1" title="?" rowspan="?"><a href="?">?</a></td> */
+		if (i === 0) {
+			td_sha1.title = commit.info;
+			td_sha1.rowSpan = group.numlines;
+
+			a_sha1.href = projectUrl + 'a=commit;h=' + commit.sha1;
+			a_sha1.firstChild.data = commit.sha1.substr(0, 8);
+			if (group.numlines >= 2) {
+				var fragment = document.createDocumentFragment();
+				var br   = document.createElement("br");
+				var text = document.createTextNode(
+					commit.author.match(/\b([A-Z])\B/g).join(''));
+				if (br && text) {
+					var elem = fragment || td_sha1;
+					elem.appendChild(br);
+					elem.appendChild(text);
+					if (fragment) {
+						td_sha1.appendChild(fragment);
+					}
+				}
+			}
+		} else {
+			//tr.removeChild(td_sha1); // DOM2 Core way
+			tr.deleteCell(0); // DOM2 HTML way
+		}
+
+		/* <td class="linenr"><a class="linenr" href="?">123</a></td> */
+		var linenr_commit =
+			('previous' in commit ? commit.previous : commit.sha1);
+		var linenr_filename =
+			('file_parent' in commit ? commit.file_parent : commit.filename);
+		a_linenr.href = projectUrl + 'a=blame_incremental' +
+			';hb=' + linenr_commit +
+			';f='  + encodeURIComponent(linenr_filename) +
+			'#l' + (group.srcline + i);
+
+		blamedLines++;
+
+		//updateProgressInfo();
+	}
+}
+
+// ----------------------------------------------------------------------
+
+var inProgress = false;   // are we processing response
+
+/**#@+
+ * @constant
+ */
+var sha1Re = /^([0-9a-f]{40}) ([0-9]+) ([0-9]+) ([0-9]+)/;
+var infoRe = /^([a-z-]+) ?(.*)/;
+var endRe  = /^END ?([^ ]*) ?(.*)/;
+/**@-*/
+
+var curCommit = new Commit();
+var curGroup  = {};
+
+var pollTimer = null;
+
+/**
+ * Parse output from 'git blame --incremental [...]', received via
+ * XMLHttpRequest from server (blamedataUrl), and call handleLine
+ * (which updates page) as soon as blame entry is completed.
+ *
+ * @param {String[]} lines: new complete lines from blamedata server
+ *
+ * @globals commits, curCommit, curGroup, t_interval_server, cmds_server
+ * @globals sha1Re, infoRe, endRe
+ */
+function processBlameLines(lines) {
+	var match;
+
+	for (var i = 0, len = lines.length; i < len; i++) {
+
+		if ((match = sha1Re.exec(lines[i]))) {
+			var sha1 = match[1];
+			var srcline  = parseInt(match[2], 10);
+			var resline  = parseInt(match[3], 10);
+			var numlines = parseInt(match[4], 10);
+
+			var c = commits[sha1];
+			if (!c) {
+				c = new Commit(sha1);
+				commits[sha1] = c;
+			}
+			curCommit = c;
+
+			curGroup.srcline = srcline;
+			curGroup.resline = resline;
+			curGroup.numlines = numlines;
+
+		} else if ((match = infoRe.exec(lines[i]))) {
+			var info = match[1];
+			var data = match[2];
+			switch (info) {
+			case 'filename':
+				curCommit.filename = unquote(data);
+				// 'filename' information terminates the entry
+				handleLine(curCommit, curGroup);
+				updateProgressInfo();
+				break;
+			case 'author':
+				curCommit.author = data;
+				break;
+			case 'author-time':
+				curCommit.authorTime = parseInt(data, 10);
+				break;
+			case 'author-tz':
+				curCommit.authorTimezone = data;
+				break;
+			case 'previous':
+				curCommit.nprevious++;
+				// store only first 'previous' header
+				if (!'previous' in curCommit) {
+					var parts = data.split(' ', 2);
+					curCommit.previous    = parts[0];
+					curCommit.file_parent = unquote(parts[1]);
+				}
+				break;
+			case 'boundary':
+				curCommit.boundary = true;
+				break;
+			} // end switch
+
+		} else if ((match = endRe.exec(lines[i]))) {
+			t_interval_server = match[1];
+			cmds_server = match[2];
+
+		} else if (lines[i] !== '') {
+			// malformed line
+
+		} // end if (match)
+
+	} // end for (lines)
+}
+
+/**
+ * Process new data and return pointer to end of processed part
+ *
+ * @param {String} unprocessed: new data (from nextReadPos)
+ * @param {Number} nextReadPos: end of last processed data
+ * @return {Number} end of processed data (new value for nextReadPos)
+ */
+function processData(unprocessed, nextReadPos) {
+	var lastLineEnd = unprocessed.lastIndexOf('\n');
+	if (lastLineEnd !== -1) {
+		var lines = unprocessed.substring(0, lastLineEnd).split('\n');
+		nextReadPos += lastLineEnd + 1 /* 1 == '\n'.length */;
+
+		processBlameLines(lines);
+	} // end if
+
+	return nextReadPos;
+}
+
+/**
+ * Handle XMLHttpRequest errors
+ *
+ * @param {XMLHttpRequest} xhr: XMLHttpRequest object
+ *
+ * @globals pollTimer, commits, inProgress
+ */
+function handleError(xhr) {
+	errorInfo('Server error: ' +
+		xhr.status + ' - ' + (xhr.statusText || 'Error contacting server'));
+
+	clearInterval(pollTimer);
+	commits = {}; // free memory
+
+	inProgress = false;
+}
+
+/**
+ * Called after XMLHttpRequest finishes (loads)
+ *
+ * @param {XMLHttpRequest} xhr: XMLHttpRequest object (unused)
+ *
+ * @globals pollTimer, commits, inProgress
+ */
+function responseLoaded(xhr) {
+	clearInterval(pollTimer);
+
+	fixColorsAndGroups();
+	writeTimeInterval();
+	commits = {}; // free memory
+
+	inProgress = false;
+}
+
+/**
+ * handler for XMLHttpRequest onreadystatechange event
+ * @see startBlame
+ *
+ * @globals xhr, inProgress
+ */
+function handleResponse() {
+
+	/*
+	 * xhr.readyState
+	 *
+	 *  Value  Constant (W3C)    Description
+	 *  -------------------------------------------------------------------
+	 *  0      UNSENT            open() has not been called yet.
+	 *  1      OPENED            send() has not been called yet.
+	 *  2      HEADERS_RECEIVED  send() has been called, and headers
+	 *                           and status are available.
+	 *  3      LOADING           Downloading; responseText holds partial data.
+	 *  4      DONE              The operation is complete.
+	 */
+
+	if (xhr.readyState !== 4 && xhr.readyState !== 3) {
+		return;
+	}
+
+	// the server returned error
+	if (xhr.readyState === 3 && xhr.status !== 200) {
+		return;
+	}
+	if (xhr.readyState === 4 && xhr.status !== 200) {
+		handleError(xhr);
+		return;
+	}
+
+	// In konqueror xhr.responseText is sometimes null here...
+	if (xhr.responseText === null) {
+		return;
+	}
+
+	// in case we were called before finished processing
+	if (inProgress) {
+		return;
+	} else {
+		inProgress = true;
+	}
+
+	// extract new whole (complete) lines, and process them
+	while (xhr.prevDataLength !== xhr.responseText.length) {
+		if (xhr.readyState === 4 &&
+		    xhr.prevDataLength === xhr.responseText.length) {
+			break;
+		}
+
+		xhr.prevDataLength = xhr.responseText.length;
+		var unprocessed = xhr.responseText.substring(xhr.nextReadPos);
+		xhr.nextReadPos = processData(unprocessed, xhr.nextReadPos);
+	} // end while
+
+	// did we finish work?
+	if (xhr.readyState === 4 &&
+	    xhr.prevDataLength === xhr.responseText.length) {
+		responseLoaded(xhr);
+	}
+
+	inProgress = false;
+}
+
+// ============================================================
+// ------------------------------------------------------------
+
+/**
+ * Incrementally update line data in blame_incremental view in gitweb.
+ *
+ * @param {String} blamedataUrl: URL to server script generating blame data.
+ * @param {String} bUrl: partial URL to project, used to generate links.
+ *
+ * Called from 'blame_incremental' view after loading table with
+ * file contents, a base for blame view.
+ *
+ * @globals xhr, t0, projectUrl, div_progress_bar, totalLines, pollTimer
+*/
+function startBlame(blamedataUrl, bUrl) {
+
+	xhr = createRequestObject();
+	if (!xhr) {
+		errorInfo('ERROR: XMLHttpRequest not supported');
+		return;
+	}
+
+	t0 = new Date();
+	projectUrl = bUrl + (bUrl.indexOf('?') === -1 ? '?' : ';');
+	if ((div_progress_bar = document.getElementById('progress_bar'))) {
+		//div_progress_bar.setAttribute('style', 'width: 100%;');
+		div_progress_bar.style.cssText = 'width: 100%;';
+	}
+	totalLines = countLines();
+	updateProgressInfo();
+
+	/* add extra properties to xhr object to help processing response */
+	xhr.prevDataLength = -1;  // used to detect if we have new data
+	xhr.nextReadPos = 0;      // where unread part of response starts
+
+	xhr.onreadystatechange = handleResponse;
+	//xhr.onreadystatechange = function () { handleResponse(xhr); };
+
+	xhr.open('GET', blamedataUrl);
+	xhr.setRequestHeader('Accept', 'text/plain');
+	xhr.send(null);
+
+	// not all browsers call onreadystatechange event on each server flush
+	// poll response using timer every second to handle this issue
+	pollTimer = setInterval(xhr.onreadystatechange, 1000);
+}
+
+// end of gitweb.js
diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl
index 08d410d..88c91ff 100755
--- a/gitweb/gitweb.perl
+++ b/gitweb/gitweb.perl
@@ -96,6 +96,8 @@ our $stylesheet = undef;
 our $logo = "++GITWEB_LOGO++";
 # URI of GIT favicon, assumed to be image/png type
 our $favicon = "++GITWEB_FAVICON++";
+# URI of gitweb.js (JavaScript code for gitweb)
+our $javascript = "++GITWEB_JS++";
 
 # URI and label (title) of GIT logo link
 #our $logo_url = "http://www.kernel.org/pub/software/scm/git/docs/";
@@ -575,6 +577,8 @@ our %cgi_param_mapping = @cgi_param_mapping;
 # we will also need to know the possible actions, for validation
 our %actions = (
 	"blame" => \&git_blame,
+	"blame_incremental" => \&git_blame_incremental,
+	"blame_data" => \&git_blame_data,
 	"blobdiff" => \&git_blobdiff,
 	"blobdiff_plain" => \&git_blobdiff_plain,
 	"blob" => \&git_blob,
@@ -4800,7 +4804,9 @@ sub git_tag {
 	git_footer_html();
 }
 
-sub git_blame {
+sub git_blame_common {
+	my $format = shift || 'porcelain';
+
 	# permissions
 	gitweb_check_feature('blame')
 		or die_error(403, "Blame view not allowed");
@@ -4822,10 +4828,43 @@ sub git_blame {
 		}
 	}
 
-	# run git-blame --porcelain
-	open my $fd, "-|", git_cmd(), "blame", '-p',
-		$hash_base, '--', $file_name
-		or die_error(500, "Open git-blame failed");
+	my $fd;
+	if ($format eq 'incremental') {
+		# get file contents (as base)
+		open $fd, "-|", git_cmd(), 'cat-file', 'blob', $hash
+			or die_error(500, "Open git-cat-file failed");
+	} elsif ($format eq 'data') {
+		# run git-blame --incremental
+		open $fd, "-|", git_cmd(), "blame", "--incremental",
+			$hash_base, "--", $file_name
+			or die_error(500, "Open git-blame --incremental failed");
+	} else {
+		# run git-blame --porcelain
+		open $fd, "-|", git_cmd(), "blame", '-p',
+			$hash_base, '--', $file_name
+			or die_error(500, "Open git-blame --porcelain failed");
+	}
+
+	# incremental blame data returns early
+	if ($format eq 'data') {
+		print $cgi->header(
+			-type=>"text/plain", -charset => "utf-8",
+			-status=> "200 OK");
+		local $| = 1; # output autoflush
+		print while <$fd>;
+		close $fd
+			or print "ERROR $!\n";
+
+		print 'END';
+		if (defined $t0 && gitweb_check_feature('timed')) {
+			print ' '.
+			      Time::HiRes::tv_interval($t0, [Time::HiRes::gettimeofday()]).
+			      ' '.$number_of_git_cmds;
+		}
+		print "\n";
+
+		return;
+	}
 
 	# page header
 	git_header_html();
@@ -4836,109 +4875,170 @@ sub git_blame {
 		$cgi->a({-href => href(action=>"history", -replay=>1)},
 		        "history") .
 		" | " .
-		$cgi->a({-href => href(action=>"blame", file_name=>$file_name)},
+		$cgi->a({-href => href(action=>$action, file_name=>$file_name)},
 		        "HEAD");
 	git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav);
 	git_print_header_div('commit', esc_html($co{'title'}), $hash_base);
 	git_print_page_path($file_name, $ftype, $hash_base);
 
 	# page body
+	if ($format eq 'incremental') {
+		print "<noscript>\n<div class=\"error\"><center><b>\n".
+		      "This page requires JavaScript to run.\n Use ".
+		      $cgi->a({-href => href(action=>'blame',-replay=>1)},
+		              'this page').
+		      " instead.\n".
+		      "</b></center></div>\n</noscript>\n";
+
+		print qq!<div id="progress_bar" style="width: 100%; background-color: yellow"></div>\n!;
+	}
+
+	print qq!<div class="page_body">\n!;
+	print qq!<div id="progress_info">... / ...</div>\n!
+		if ($format eq 'incremental');
+	print qq!<table id="blame_table" class="blame" width="100%">\n!.
+	      #qq!<col width="5.5em" /><col width="2.5em" /><col width="*" />\n!.
+	      qq!<thead>\n!.
+	      qq!<tr><th>Commit</th><th>Line</th><th>Data</th></tr>\n!.
+	      qq!</thead>\n!.
+	      qq!<tbody>\n!;
+
 	my @rev_color = qw(light dark);
 	my $num_colors = scalar(@rev_color);
 	my $current_color = 0;
-	my %metainfo = ();
 
-	print <<HTML;
-<div class="page_body">
-<table class="blame">
-<tr><th>Commit</th><th>Line</th><th>Data</th></tr>
-HTML
- LINE:
-	while (my $line = <$fd>) {
-		chomp $line;
-		# the header: <SHA-1> <src lineno> <dst lineno> [<lines in group>]
-		# no <lines in group> for subsequent lines in group of lines
-		my ($full_rev, $orig_lineno, $lineno, $group_size) =
-		   ($line =~ /^([0-9a-f]{40}) (\d+) (\d+)(?: (\d+))?$/);
-		if (!exists $metainfo{$full_rev}) {
-			$metainfo{$full_rev} = { 'nprevious' => 0 };
-		}
-		my $meta = $metainfo{$full_rev};
-		my $data;
-		while ($data = <$fd>) {
-			chomp $data;
-			last if ($data =~ s/^\t//); # contents of line
-			if ($data =~ /^(\S+)(?: (.*))?$/) {
-				$meta->{$1} = $2 unless exists $meta->{$1};
+	if ($format eq 'incremental') {
+		my $color_class = $rev_color[$current_color];
+
+		#contents of a file
+		my $linenr = 0;
+	LINE:
+		while (my $line = <$fd>) {
+			chomp $line;
+			$linenr++;
+
+			print qq!<tr id="l$linenr" class="$color_class">!.
+			      qq!<td class="sha1"><a href=""> </a></td>!.
+			      qq!<td class="linenr">!.
+			      qq!<a class="linenr" href="">$linenr</a></td>!;
+			print qq!<td class="pre">! . esc_html($line) . "</td>\n";
+			print qq!</tr>\n!;
+		}
+
+	} else { # porcelain, i.e. ordinary blame
+		my %metainfo = (); # saves information about commits
+
+		# blame data
+	LINE:
+		while (my $line = <$fd>) {
+			chomp $line;
+			# the header: <SHA-1> <src lineno> <dst lineno> [<lines in group>]
+			# no <lines in group> for subsequent lines in group of lines
+			my ($full_rev, $orig_lineno, $lineno, $group_size) =
+			   ($line =~ /^([0-9a-f]{40}) (\d+) (\d+)(?: (\d+))?$/);
+			if (!exists $metainfo{$full_rev}) {
+				$metainfo{$full_rev} = { 'nprevious' => 0 };
 			}
-			if ($data =~ /^previous /) {
-				$meta->{'nprevious'}++;
+			my $meta = $metainfo{$full_rev};
+			my $data;
+			while ($data = <$fd>) {
+				chomp $data;
+				last if ($data =~ s/^\t//); # contents of line
+				if ($data =~ /^(\S+)(?: (.*))?$/) {
+					$meta->{$1} = $2 unless exists $meta->{$1};
+				}
+				if ($data =~ /^previous /) {
+					$meta->{'nprevious'}++;
+				}
 			}
-		}
-		my $short_rev = substr($full_rev, 0, 8);
-		my $author = $meta->{'author'};
-		my %date =
-			parse_date($meta->{'author-time'}, $meta->{'author-tz'});
-		my $date = $date{'iso-tz'};
-		if ($group_size) {
-			$current_color = ($current_color + 1) % $num_colors;
-		}
-		my $tr_class = $rev_color[$current_color];
-		$tr_class .= ' boundary' if (exists $meta->{'boundary'});
-		$tr_class .= ' no-previous' if ($meta->{'nprevious'} == 0);
-		$tr_class .= ' multiple-previous' if ($meta->{'nprevious'} > 1);
-		print "<tr id=\"l$lineno\" class=\"$tr_class\">\n";
-		if ($group_size) {
-			print "<td class=\"sha1\"";
-			print " title=\"". esc_html($author) . ", $date\"";
-			print " rowspan=\"$group_size\"" if ($group_size > 1);
-			print ">";
-			print $cgi->a({-href => href(action=>"commit",
-			                             hash=>$full_rev,
-			                             file_name=>$file_name)},
-			              esc_html($short_rev));
-			if ($group_size >= 2) {
-				my @author_initials = ($author =~ /\b([[:upper:]])\B/g);
-				if (@author_initials) {
-					print "<br />" .
-					      esc_html(join('', @author_initials));
-					#           or join('.', ...)
+			my $short_rev = substr($full_rev, 0, 8);
+			my $author = $meta->{'author'};
+			my %date =
+				parse_date($meta->{'author-time'}, $meta->{'author-tz'});
+			my $date = $date{'iso-tz'};
+			if ($group_size) {
+				$current_color = ($current_color + 1) % $num_colors;
+			}
+			my $tr_class = $rev_color[$current_color];
+			$tr_class .= ' boundary' if (exists $meta->{'boundary'});
+			$tr_class .= ' no-previous' if ($meta->{'nprevious'} == 0);
+			$tr_class .= ' multiple-previous' if ($meta->{'nprevious'} > 1);
+			print "<tr id=\"l$lineno\" class=\"$tr_class\">\n";
+			if ($group_size) {
+				print "<td class=\"sha1\"";
+				print " title=\"". esc_html($author) . ", $date\"";
+				print " rowspan=\"$group_size\"" if ($group_size > 1);
+				print ">";
+				print $cgi->a({-href => href(action=>"commit",
+				                             hash=>$full_rev,
+				                             file_name=>$file_name)},
+				              esc_html($short_rev));
+				if ($group_size >= 2) {
+					my @author_initials = ($author =~ /\b([[:upper:]])\B/g);
+					if (@author_initials) {
+						print "<br />" .
+						      esc_html(join('', @author_initials));
+						#           or join('.', ...)
+					}
 				}
+				print "</td>\n";
 			}
-			print "</td>\n";
-		}
-		# 'previous' <sha1 of parent commit> <filename at commit>
-		if (exists $meta->{'previous'} &&
-		    $meta->{'previous'} =~ /^([a-fA-F0-9]{40}) (.*)$/) {
-			$meta->{'parent'} = $1;
-			$meta->{'file_parent'} = unquote($2);
-		}
-		my $linenr_commit =
-			exists($meta->{'parent'}) ?
-			$meta->{'parent'} : $full_rev;
-		my $linenr_filename =
-			exists($meta->{'file_parent'}) ?
-			$meta->{'file_parent'} : unquote($meta->{'filename'});
-		my $blamed = href(action => 'blame',
-		                  file_name => $linenr_filename,
-		                  hash_base => $linenr_commit);
-		print "<td class=\"linenr\">";
-		print $cgi->a({ -href => "$blamed#l$orig_lineno",
-		                -class => "linenr" },
-		              esc_html($lineno));
-		print "</td>";
-		print "<td class=\"pre\">" . esc_html($data) . "</td>\n";
-		print "</tr>\n";
+			# 'previous' <sha1 of parent commit> <filename at commit>
+			if (exists $meta->{'previous'} &&
+			    $meta->{'previous'} =~ /^([a-fA-F0-9]{40}) (.*)$/) {
+				$meta->{'parent'} = $1;
+				$meta->{'file_parent'} = unquote($2);
+			}
+			my $linenr_commit =
+				exists($meta->{'parent'}) ?
+				$meta->{'parent'} : $full_rev;
+			my $linenr_filename =
+				exists($meta->{'file_parent'}) ?
+				$meta->{'file_parent'} : unquote($meta->{'filename'});
+			my $blamed = href(action => 'blame',
+			                  file_name => $linenr_filename,
+			                  hash_base => $linenr_commit);
+			print "<td class=\"linenr\">";
+			print $cgi->a({ -href => "$blamed#l$orig_lineno",
+			                -class => "linenr" },
+			              esc_html($lineno));
+			print "</td>";
+			print "<td class=\"pre\">" . esc_html($data) . "</td>\n";
+			print "</tr>\n";
+		} # end while
+
 	}
-	print "</table>\n";
-	print "</div>";
+
+	# footer
+	print "</tbody>\n".
+	      "</table>\n"; # class="blame"
+	print "</div>\n";   # class="blame_body"
 	close $fd
 		or print "Reading blob failed\n";
 
-	# page footer
+	if ($format eq 'incremental') {
+		print qq!<script type="text/javascript" src="$javascript"></script>\n!.
+		      qq!<script type="text/javascript">\n!.
+		      qq!startBlame("!. href(action=>"blame_data", -replay=>1) .qq!",\n!.
+		      qq!           "!. href() .qq!");\n!.
+		      qq!</script>\n!;
+	}
+
 	git_footer_html();
 }
 
+sub git_blame {
+	git_blame_common();
+}
+
+sub git_blame_incremental {
+	git_blame_common('incremental');
+}
+
+sub git_blame_data {
+	git_blame_common('data');
+}
+
 sub git_tags {
 	my $head = git_get_head_hash($project);
 	git_header_html();
-- 
1.6.3.3

^ permalink raw reply related


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox