summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/Makefile1
-rw-r--r--Documentation/config.txt8
-rw-r--r--Documentation/git-bisect-lk2009.txt1358
-rw-r--r--Documentation/git-bisect.txt5
-rw-r--r--Documentation/git-commit.txt7
-rw-r--r--Documentation/git-mailinfo.txt7
-rw-r--r--Documentation/git-send-email.txt7
-rw-r--r--Documentation/pretty-formats.txt8
-rw-r--r--Makefile26
-rw-r--r--builtin-commit.c12
-rw-r--r--builtin-log.c3
-rw-r--r--builtin-mailinfo.c49
-rw-r--r--builtin-merge.c3
-rw-r--r--builtin-shortlog.c19
-rw-r--r--combine-diff.c5
-rw-r--r--diff.c56
-rw-r--r--diff.h1
-rwxr-xr-xgit-am.sh11
-rwxr-xr-xgit-instaweb.sh7
-rwxr-xr-xgit-send-email.perl23
-rw-r--r--gitweb/README4
-rw-r--r--gitweb/gitweb.css23
-rw-r--r--gitweb/gitweb.js870
-rwxr-xr-xgitweb/gitweb.perl331
-rw-r--r--merge-recursive.c41
-rw-r--r--merge-recursive.h3
-rw-r--r--pretty.c42
-rwxr-xr-xt/t4014-format-patch.sh5
-rwxr-xr-xt/t4034-diff-words.sh12
-rwxr-xr-xt/t4201-shortlog.sh28
-rwxr-xr-xt/t6006-rev-list-format.sh22
-rwxr-xr-xt/t7509-commit.sh114
-rwxr-xr-xt/t9001-send-email.sh83
33 files changed, 3021 insertions, 173 deletions
diff --git a/Documentation/Makefile b/Documentation/Makefile
index cd5b4396db..3f599524ea 100644
--- a/Documentation/Makefile
+++ b/Documentation/Makefile
@@ -17,6 +17,7 @@ DOC_HTML=$(MAN_HTML)
ARTICLES = howto-index
ARTICLES += everyday
ARTICLES += git-tools
+ARTICLES += git-bisect-lk2009
# with their own formatting rules.
SP_ARTICLES = howto/revert-branch-rebase howto/using-merge-subtree user-manual
API_DOCS = $(patsubst %.txt,%,$(filter-out technical/api-index-skel.txt technical/api-index.txt, $(wildcard technical/api-*.txt)))
diff --git a/Documentation/config.txt b/Documentation/config.txt
index a8e0876a2a..a1e36d7e42 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -635,10 +635,10 @@ color.diff.<slot>::
Use customized color for diff colorization. `<slot>` specifies
which part of the patch to use the specified color, and is one
of `plain` (context text), `meta` (metainformation), `frag`
- (hunk header), `old` (removed lines), `new` (added lines),
- `commit` (commit headers), or `whitespace` (highlighting
- whitespace errors). The values of these variables may be specified as
- in color.branch.<slot>.
+ (hunk header), 'func' (function in hunk header), `old` (removed lines),
+ `new` (added lines), `commit` (commit headers), or `whitespace`
+ (highlighting whitespace errors). The values of these variables may be
+ specified as in color.branch.<slot>.
color.grep::
When set to `always`, always highlight matches. When `false` (or
diff --git a/Documentation/git-bisect-lk2009.txt b/Documentation/git-bisect-lk2009.txt
new file mode 100644
index 0000000000..6b7b2e5497
--- /dev/null
+++ b/Documentation/git-bisect-lk2009.txt
@@ -0,0 +1,1358 @@
+Fighting regressions with git bisect
+====================================
+:Author: Christian Couder
+:Email: chriscool@tuxfamily.org
+:Date: 2009/11/08
+
+Abstract
+--------
+
+"git bisect" enables software users and developers to easily find the
+commit that introduced a regression. We show why it is important to
+have good tools to fight regressions. We describe how "git bisect"
+works from the outside and the algorithms it uses inside. Then we
+explain how to take advantage of "git bisect" to improve current
+practices. And we discuss how "git bisect" could improve in the
+future.
+
+
+Introduction to "git bisect"
+----------------------------
+
+Git is a Distributed Version Control system (DVCS) created by Linus
+Torvalds and maintained by Junio Hamano.
+
+In Git like in many other Version Control Systems (VCS), the different
+states of the data that is managed by the system are called
+commits. And, as VCS are mostly used to manage software source code,
+sometimes "interesting" changes of behavior in the software are
+introduced in some commits.
+
+In fact people are specially interested in commits that introduce a
+"bad" behavior, called a bug or a regression. They are interested in
+these commits because a commit (hopefully) contains a very small set
+of source code changes. And it's much easier to understand and
+properly fix a problem when you only need to check a very small set of
+changes, than when you don't know where look in the first place.
+
+So to help people find commits that introduce a "bad" behavior, the
+"git bisect" set of commands was invented. And it follows of course
+that in "git bisect" parlance, commits where the "interesting
+behavior" is present are called "bad" commits, while other commits are
+called "good" commits. And a commit that introduce the behavior we are
+interested in is called a "first bad commit". Note that there could be
+more than one "first bad commit" in the commit space we are searching.
+
+So "git bisect" is designed to help find a "first bad commit". And to
+be as efficient as possible, it tries to perform a binary search.
+
+
+Fighting regressions overview
+-----------------------------
+
+Regressions: a big problem
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Regressions are a big problem in the software industry. But it's
+difficult to put some real numbers behind that claim.
+
+There are some numbers about bugs in general, like a NIST study in
+2002 <<1>> that said:
+
+_____________
+Software bugs, or errors, are so prevalent and so detrimental that
+they cost the U.S. economy an estimated $59.5 billion annually, or
+about 0.6 percent of the gross domestic product, according to a newly
+released study commissioned by the Department of Commerce's National
+Institute of Standards and Technology (NIST). At the national level,
+over half of the costs are borne by software users and the remainder
+by software developers/vendors. The study also found that, although
+all errors cannot be removed, more than a third of these costs, or an
+estimated $22.2 billion, could be eliminated by an improved testing
+infrastructure that enables earlier and more effective identification
+and removal of software defects. These are the savings associated with
+finding an increased percentage (but not 100 percent) of errors closer
+to the development stages in which they are introduced. Currently,
+over half of all errors are not found until "downstream" in the
+development process or during post-sale software use.
+_____________
+
+And then:
+
+_____________
+Software developers already spend approximately 80 percent of
+development costs on identifying and correcting defects, and yet few
+products of any type other than software are shipped with such high
+levels of errors.
+_____________
+
+Eventually the conclusion started with:
+
+_____________
+The path to higher software quality is significantly improved software
+testing.
+_____________
+
+There are other estimates saying that 80% of the cost related to
+software is about maintenance <<2>>.
+
+Though, according to Wikipedia <<3>>:
+
+_____________
+A common perception of maintenance is that it is merely fixing
+bugs. However, studies and surveys over the years have indicated that
+the majority, over 80%, of the maintenance effort is used for
+non-corrective actions (Pigosky 1997). This perception is perpetuated
+by users submitting problem reports that in reality are functionality
+enhancements to the system.
+_____________
+
+But we can guess that improving on existing software is very costly
+because you have to watch out for regressions. At least this would
+make the above studies consistent among themselves.
+
+Of course some kind of software is developed, then used during some
+time without being improved on much, and then finally thrown away. In
+this case, of course, regressions may not be a big problem. But on the
+other hand, there is a lot of big software that is continually
+developed and maintained during years or even tens of years by a lot
+of people. And as there are often many people who depend (sometimes
+critically) on such software, regressions are a really big problem.
+
+One such software is the linux kernel. And if we look at the linux
+kernel, we can see that a lot of time and effort is spent to fight
+regressions. The release cycle start with a 2 weeks long merge
+window. Then the first release candidate (rc) version is tagged. And
+after that about 7 or 8 more rc versions will appear with around one
+week between each of them, before the final release.
+
+The time between the first rc release and the final release is
+supposed to be used to test rc versions and fight bugs and especially
+regressions. And this time is more than 80% of the release cycle
+time. But this is not the end of the fight yet, as of course it
+continues after the release.
+
+And then this is what Ingo Molnar (a well known linux kernel
+developer) says about his use of git bisect:
+
+_____________
+I most actively use it during the merge window (when a lot of trees
+get merged upstream and when the influx of bugs is the highest) - and
+yes, there have been cases that i used it multiple times a day. My
+average is roughly once a day.
+_____________
+
+So regressions are fought all the time by developers, and indeed it is
+well known that bugs should be fixed as soon as possible, so as soon
+as they are found. That's why it is interesting to have good tools for
+this purpose.
+
+Other tools to fight regressions
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+So what are the tools used to fight regressions? They are nearly the
+same as those used to fight regular bugs. The only specific tools are
+test suites and tools similar as "git bisect".
+
+Test suites are very nice. But when they are used alone, they are
+supposed to be used so that all the tests are checked after each
+commit. This means that they are not very efficient, because many
+tests are run for no interesting result, and they suffer from
+combinational explosion.
+
+In fact the problem is that big software often has many different
+configuration options and that each test case should pass for each
+configuration after each commit. So if you have for each release: N
+configurations, M commits and T test cases, you should perform:
+
+-------------
+N * M * T tests
+-------------
+
+where N, M and T are all growing with the size your software.
+
+So very soon it will not be possible to completely test everything.
+
+And if some bugs slip through your test suite, then you can add a test
+to your test suite. But if you want to use your new improved test
+suite to find where the bug slipped in, then you will either have to
+emulate a bisection process or you will perhaps bluntly test each
+commit backward starting from the "bad" commit you have which may be
+very wasteful.
+
+"git bisect" overview
+---------------------
+
+Starting a bisection
+~~~~~~~~~~~~~~~~~~~~
+
+The first "git bisect" subcommand to use is "git bisect start" to
+start the search. Then bounds must be set to limit the commit
+space. This is done usually by giving one "bad" and at least one
+"good" commit. They can be passed in the initial call to "git bisect
+start" like this:
+
+-------------
+$ git bisect start [BAD [GOOD...]]
+-------------
+
+or they can be set using:
+
+-------------
+$ git bisect bad [COMMIT]
+-------------
+
+and:
+
+-------------
+$ git bisect good [COMMIT...]
+-------------
+
+where BAD, GOOD and COMMIT are all names that can be resolved to a
+commit.
+
+Then "git bisect" will checkout a commit of its choosing and ask the
+user to test it, like this:
+
+-------------
+$ git bisect start v2.6.27 v2.6.25
+Bisecting: 10928 revisions left to test after this (roughly 14 steps)
+[2ec65f8b89ea003c27ff7723525a2ee335a2b393] x86: clean up using max_low_pfn on 32-bit
+-------------
+
+Note that the example that we will use is really a toy example, we
+will be looking for the first commit that has a version like
+"2.6.26-something", that is the commit that has a "SUBLEVEL = 26" line
+in the top level Makefile. This is a toy example because there are
+better ways to find this commit with git than using "git bisect" (for
+example "git blame" or "git log -S<string>").
+
+Driving a bisection manually
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+At this point there are basically 2 ways to drive the search. It can
+be driven manually by the user or it can be driven automatically by a
+script or a command.
+
+If the user is driving it, then at each step of the search, the user
+will have to test the current commit and say if it is "good" or "bad"
+using the "git bisect good" or "git bisect bad" commands respectively
+that have been described above. For example:
+
+-------------
+$ git bisect bad
+Bisecting: 5480 revisions left to test after this (roughly 13 steps)
+[66c0b394f08fd89236515c1c84485ea712a157be] KVM: kill file->f_count abuse in kvm
+-------------
+
+And after a few more steps like that, "git bisect" will eventually
+find a first bad commit:
+
+-------------
+$ git bisect bad
+2ddcca36c8bcfa251724fe342c8327451988be0d is the first bad commit
+commit 2ddcca36c8bcfa251724fe342c8327451988be0d
+Author: Linus Torvalds <torvalds@linux-foundation.org>
+Date: Sat May 3 11:59:44 2008 -0700
+
+ Linux 2.6.26-rc1
+
+:100644 100644 5cf8258195331a4dbdddff08b8d68642638eea57 4492984efc09ab72ff6219a7bc21fb6a957c4cd5 M Makefile
+-------------
+
+At this point we can see what the commit does, check it out (if it's
+not already checked out) or tinker with it, for example:
+
+-------------
+$ git show HEAD
+commit 2ddcca36c8bcfa251724fe342c8327451988be0d
+Author: Linus Torvalds <torvalds@linux-foundation.org>
+Date: Sat May 3 11:59:44 2008 -0700
+
+ Linux 2.6.26-rc1
+
+diff --git a/Makefile b/Makefile
+index 5cf8258..4492984 100644
+--- a/Makefile
++++ b/Makefile
+@@ -1,7 +1,7 @@
+ VERSION = 2
+ PATCHLEVEL = 6
+-SUBLEVEL = 25
+-EXTRAVERSION =
++SUBLEVEL = 26
++EXTRAVERSION = -rc1
+ NAME = Funky Weasel is Jiggy wit it
+
+ # *DOCUMENTATION*
+-------------
+
+And when we are finished we can use "git bisect reset" to go back to
+the branch we were in before we started bisecting:
+
+-------------
+$ git bisect reset
+Checking out files: 100% (21549/21549), done.
+Previous HEAD position was 2ddcca3... Linux 2.6.26-rc1
+Switched to branch 'master'
+-------------
+
+Driving a bisection automatically
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The other way to drive the bisection process is to tell "git bisect"
+to launch a script or command at each bisection step to know if the
+current commit is "good" or "bad". To do that, we use the "git bisect
+run" command. For example:
+
+-------------
+$ git bisect start v2.6.27 v2.6.25
+Bisecting: 10928 revisions left to test after this (roughly 14 steps)
+[2ec65f8b89ea003c27ff7723525a2ee335a2b393] x86: clean up using max_low_pfn on 32-bit
+$
+$ git bisect run grep '^SUBLEVEL = 25' Makefile
+running grep ^SUBLEVEL = 25 Makefile
+Bisecting: 5480 revisions left to test after this (roughly 13 steps)
+[66c0b394f08fd89236515c1c84485ea712a157be] KVM: kill file->f_count abuse in kvm
+running grep ^SUBLEVEL = 25 Makefile
+SUBLEVEL = 25
+Bisecting: 2740 revisions left to test after this (roughly 12 steps)
+[671294719628f1671faefd4882764886f8ad08cb] V4L/DVB(7879): Adding cx18 Support for mxl5005s
+...
+...
+running grep ^SUBLEVEL = 25 Makefile
+Bisecting: 0 revisions left to test after this (roughly 0 steps)
+[2ddcca36c8bcfa251724fe342c8327451988be0d] Linux 2.6.26-rc1
+running grep ^SUBLEVEL = 25 Makefile
+2ddcca36c8bcfa251724fe342c8327451988be0d is the first bad commit
+commit 2ddcca36c8bcfa251724fe342c8327451988be0d
+Author: Linus Torvalds <torvalds@linux-foundation.org>
+Date: Sat May 3 11:59:44 2008 -0700
+
+ Linux 2.6.26-rc1
+
+:100644 100644 5cf8258195331a4dbdddff08b8d68642638eea57 4492984efc09ab72ff6219a7bc21fb6a957c4cd5 M Makefile
+bisect run success
+-------------
+
+In this example, we passed "grep '^SUBLEVEL = 25' Makefile" as
+parameter to "git bisect run". This means that at each step, the grep
+command we passed will be launched. And if it exits with code 0 (that
+means success) then git bisect will mark the current state as
+"good". If it exits with code 1 (or any code between 1 and 127
+included, except the special code 125), then the current state will be
+marked as "bad".
+
+Exit code between 128 and 255 are special to "git bisect run". They
+make it stop immediately the bisection process. This is useful for
+example if the command passed takes too long to complete, because you
+can kill it with a signal and it will stop the bisection process.
+
+It can also be useful in scripts passed to "git bisect run" to "exit
+255" if some very abnormal situation is detected.
+
+Avoiding untestable commits
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Sometimes it happens that the current state cannot be tested, for
+example if it does not compile because there was a bug preventing it
+at that time. This is what the special exit code 125 is for. It tells
+"git bisect run" that the current commit should be marked as
+untestable and that another one should be chosen and checked out.
+
+If the bisection process is driven manually, you can use "git bisect
+skip" to do the same thing. (In fact the special exit code 125 makes
+"git bisect run" use "git bisect skip" in the background.)
+
+Or if you want more control, you can inspect the current state using
+for example "git bisect visualize". It will launch gitk (or "git log"
+if the DISPLAY environment variable is not set) to help you find a
+better bisection point.
+
+Either way, if you have a string of untestable commits, it might
+happen that the regression you are looking for has been introduced by
+one of these untestable commits. In this case it's not possible to
+tell for sure which commit introduced the regression.
+
+So if you used "git bisect skip" (or the run script exited with
+special code 125) you could get a result like this:
+
+-------------
+There are only 'skip'ped commits left to test.
+The first bad commit could be any of:
+15722f2fa328eaba97022898a305ffc8172db6b1
+78e86cf3e850bd755bb71831f42e200626fbd1e0
+e15b73ad3db9b48d7d1ade32f8cd23a751fe0ace
+070eab2303024706f2924822bfec8b9847e4ac1b
+We cannot bisect more!
+-------------
+
+Saving a log and replaying it
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If you want to show other people your bisection process, you can get a
+log using for example:
+
+-------------
+$ git bisect log > bisect_log.txt
+-------------
+
+And it is possible to replay it using:
+
+-------------
+$ git bisect replay bisect_log.txt
+-------------
+
+
+"git bisect" details
+--------------------
+
+Bisection algorithm
+~~~~~~~~~~~~~~~~~~~
+
+As the Git commits form a directed acyclic graph (DAG), finding the
+best bisection commit to test at each step is not so simple. Anyway
+Linus found and implemented a "truly stupid" algorithm, later improved
+by Junio Hamano, that works quite well.
+
+So the algorithm used by "git bisect" to find the best bisection
+commit when there are no skipped commits is the following:
+
+1) keep only the commits that:
+
+a) are ancestor of the "bad" commit (including the "bad" commit itself),
+b) are not ancestor of a "good" commit (excluding the "good" commits).
+
+This means that we get rid of the uninteresting commits in the DAG.
+
+For example if we start with a graph like this:
+
+-------------
+G-Y-G-W-W-W-X-X-X-X
+ \ /
+ W-W-B
+ /
+Y---G-W---W
+ \ / \
+Y-Y X-X-X-X
+
+-> time goes this way ->
+-------------
+
+where B is the "bad" commit, "G" are "good" commits and W, X, and Y
+are other commits, we will get the following graph after this first
+step:
+
+-------------
+W-W-W
+ \
+ W-W-B
+ /
+W---W
+-------------
+
+So only the W and B commits will be kept. Because commits X and Y will
+have been removed by rules a) and b) respectively, and because commits
+G are removed by rule b) too.
+
+Note for git users, that it is equivalent as keeping only the commit
+given by:
+
+-------------
+git rev-list BAD --not GOOD1 GOOD2...
+-------------
+
+Also note that we don't require the commits that are kept to be
+descendants of a "good" commit. So in the following example, commits W
+and Z will be kept:
+
+-------------
+G-W-W-W-B
+ /
+Z-Z
+-------------
+
+2) starting from the "good" ends of the graph, associate to each
+commit the number of ancestors it has plus one
+
+For example with the following graph where H is the "bad" commit and A
+and D are some parents of some "good" commits:
+
+-------------
+A-B-C
+ \
+ F-G-H
+ /
+D---E
+-------------
+
+this will give:
+
+-------------
+1 2 3
+A-B-C
+ \6 7 8
+ F-G-H
+1 2/
+D---E
+-------------
+
+3) associate to each commit: min(X, N - X)
+
+where X is the value associated to the commit in step 2) and N is the
+total number of commits in the graph.
+
+In the above example we have N = 8, so this will give:
+
+-------------
+1 2 3
+A-B-C
+ \2 1 0
+ F-G-H
+1 2/
+D---E
+-------------
+
+4) the best bisection point is the commit with the highest associated
+number
+
+So in the above example the best bisection point is commit C.
+
+5) note that some shortcuts are implemented to speed up the algorithm
+
+As we know N from the beginning, we know that min(X, N - X) can't be
+greater than N/2. So during steps 2) and 3), if we would associate N/2
+to a commit, then we know this is the best bisection point. So in this
+case we can just stop processing any other commit and return the
+current commit.
+
+Bisection algorithm debugging
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+For any commit graph, you can see the number associated with each
+commit using "git rev-list --bisect-all".
+
+For example, for the above graph, a command like:
+
+-------------
+$ git rev-list --bisect-all BAD --not GOOD1 GOOD2
+-------------
+
+would output something like:
+
+-------------
+e15b73ad3db9b48d7d1ade32f8cd23a751fe0ace (dist=3)
+15722f2fa328eaba97022898a305ffc8172db6b1 (dist=2)
+78e86cf3e850bd755bb71831f42e200626fbd1e0 (dist=2)
+a1939d9a142de972094af4dde9a544e577ddef0e (dist=2)
+070eab2303024706f2924822bfec8b9847e4ac1b (dist=1)
+a3864d4f32a3bf5ed177ddef598490a08760b70d (dist=1)
+a41baa717dd74f1180abf55e9341bc7a0bb9d556 (dist=1)
+9e622a6dad403b71c40979743bb9d5be17b16bd6 (dist=0)
+-------------
+
+Bisection algorithm discussed
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+First let's define "best bisection point". We will say that a commit X
+is a best bisection point or a best bisection commit if knowing its
+state ("good" or "bad") gives as much information as possible whether
+the state of the commit happens to be "good" or "bad".
+
+This means that the best bisection commits are the commits where the
+following function is maximum:
+
+-------------
+f(X) = min(information_if_good(X), information_if_bad(X))
+-------------
+
+where information_if_good(X) is the information we get if X is good
+and information_if_bad(X) is the information we get if X is bad.
+
+Now we will suppose that there is only one "first bad commit". This
+means that all its descendants are "bad" and all the other commits are
+"good". And we will suppose that all commits have an equal probability
+of being good or bad, or of being the first bad commit, so knowing the
+state of c commits gives always the same amount of information
+wherever these c commits are on the graph and whatever c is. (So we
+suppose that these commits being for example on a branch or near a
+good or a bad commit does not give more or less information).
+
+Let's also suppose that we have a cleaned up graph like one after step
+1) in the bisection algorithm above. This means that we can measure
+the information we get in terms of number of commit we can remove from
+the graph..
+
+And let's take a commit X in the graph.
+
+If X is found to be "good", then we know that its ancestors are all
+"good", so we want to say that:
+
+-------------
+information_if_good(X) = number_of_ancestors(X) (TRUE)
+-------------
+
+And this is true because at step 1) b) we remove the ancestors of the
+"good" commits.
+
+If X is found to be "bad", then we know that its descendants are all
+"bad", so we want to say that:
+
+-------------
+information_if_bad(X) = number_of_descendants(X) (WRONG)
+-------------
+
+But this is wrong because at step 1) a) we keep only the ancestors of
+the bad commit. So we get more information when a commit is marked as
+"bad", because we also know that the ancestors of the previous "bad"
+commit that are not ancestors of the new "bad" commit are not the
+first bad commit. We don't know if they are good or bad, but we know
+that they are not the first bad commit because they are not ancestor
+of the new "bad" commit.
+
+So when a commit is marked as "bad" we know we can remove all the
+commits in the graph except those that are ancestors of the new "bad"
+commit. This means that:
+
+-------------
+information_if_bad(X) = N - number_of_ancestors(X) (TRUE)
+-------------
+
+where N is the number of commits in the (cleaned up) graph.
+
+So in the end this means that to find the best bisection commits we
+should maximize the function:
+
+-------------
+f(X) = min(number_of_ancestors(X), N - number_of_ancestors(X))
+-------------
+
+And this is nice because at step 2) we compute number_of_ancestors(X)
+and so at step 3) we compute f(X).
+
+Let's take the following graph as an example:
+
+-------------
+ G-H-I-J
+ / \
+A-B-C-D-E-F O
+ \ /
+ K-L-M-N
+-------------
+
+If we compute the following non optimal function on it:
+
+-------------
+g(X) = min(number_of_ancestors(X), number_of_descendants(X))
+-------------
+
+we get:
+
+-------------
+ 4 3 2 1
+ G-H-I-J
+1 2 3 4 5 6/ \0
+A-B-C-D-E-F O
+ \ /
+ K-L-M-N
+ 4 3 2 1
+-------------
+
+but with the algorithm used by git bisect we get:
+
+-------------
+ 7 7 6 5
+ G-H-I-J
+1 2 3 4 5 6/ \0
+A-B-C-D-E-F O
+ \ /
+ K-L-M-N
+ 7 7 6 5
+-------------
+
+So we chose G, H, K or L as the best bisection point, which is better
+than F. Because if for example L is bad, then we will know not only
+that L, M and N are bad but also that G, H, I and J are not the first
+bad commit (since we suppose that there is only one first bad commit
+and it must be an ancestor of L).
+
+So the current algorithm seems to be the best possible given what we
+initially supposed.
+
+Skip algorithm
+~~~~~~~~~~~~~~
+
+When some commits have been skipped (using "git bisect skip"), then
+the bisection algorithm is the same for step 1) to 3). But then we use
+roughly the following steps:
+
+6) sort the commit by decreasing associated value
+
+7) if the first commit has not been skipped, we can return it and stop
+here
+
+8) otherwise filter out all the skipped commits in the sorted list
+
+9) use a pseudo random number generator (PRNG) to generate a random
+number between 0 and 1
+
+10) multiply this random number with its square root to bias it toward
+0
+
+11) multiply the result by the number of commits in the filtered list
+to get an index into this list
+
+12) return the commit at the computed index
+
+Skip algorithm discussed
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+After step 7) (in the skip algorithm), we could check if the second
+commit has been skipped and return it if it is not the case. And in
+fact that was the algorithm we used from when "git bisect skip" was
+developed in git version 1.5.4 (released on February 1st 2008) until
+git version 1.6.4 (released July 29th 2009).
+
+But Ingo Molnar and H. Peter Anvin (another well known linux kernel
+developer) both complained that sometimes the best bisection points
+all happened to be in an area where all the commits are
+untestable. And in this case the user was asked to test many
+untestable commits, which could be very inefficient.
+
+Indeed untestable commits are often untestable because a breakage was
+introduced at one time, and that breakage was fixed only after many
+other commits were introduced.
+
+This breakage is of course most of the time unrelated to the breakage
+we are trying to locate in the commit graph. But it prevents us to
+know if the interesting "bad behavior" is present or not.
+
+So it is a fact that commits near an untestable commit have a high
+probability of being untestable themselves. And the best bisection
+commits are often found together too (due to the bisection algorithm).
+
+This is why it is a bad idea to just chose the next best unskipped
+bisection commit when the first one has been skipped.
+
+We found that most commits on the graph may give quite a lot of
+information when they are tested. And the commits that will not on
+average give a lot of information are the one near the good and bad
+commits.
+
+So using a PRNG with a bias to favor commits away from the good and
+bad commits looked like a good choice.
+
+One obvious improvement to this algorithm would be to look for a
+commit that has an associated value near the one of the best bisection
+commit, and that is on another branch, before using the PRNG. Because
+if such a commit exists, then it is not very likely to be untestable
+too, so it will probably give more information than a nearly randomly
+chosen one.
+
+Checking merge bases
+~~~~~~~~~~~~~~~~~~~~
+
+There is another tweak in the bisection algorithm that has not been
+described in the "bisection algorithm" above.
+
+We supposed in the previous examples that the "good" commits were
+ancestors of the "bad" commit. But this is not a requirement of "git
+bisect".
+
+Of course the "bad" commit cannot be an ancestor of a "good" commit,
+because the ancestors of the good commits are supposed to be
+"good". And all the "good" commits must be related to the bad commit.
+They cannot be on a branch that has no link with the branch of the
+"bad" commit. But it is possible for a good commit to be related to a
+bad commit and yet not be neither one of its ancestor nor one of its
+descendants.
+
+For example, there can be a "main" branch, and a "dev" branch that was
+forked of the main branch at a commit named "D" like this:
+
+-------------
+A-B-C-D-E-F-G <--main
+ \
+ H-I-J <--dev
+-------------
+
+The commit "D" is called a "merge base" for branch "main" and "dev"
+because it's the best common ancestor for these branches for a merge.
+
+Now let's suppose that commit J is bad and commit G is good and that
+we apply the bisection algorithm like it has been previously
+described.
+
+As described in step 1) b) of the bisection algorithm, we remove all
+the ancestors of the good commits because they are supposed to be good
+too.
+
+So we would be left with only:
+
+-------------
+H-I-J
+-------------
+
+But what happens if the first bad commit is "B" and if it has been
+fixed in the "main" branch by commit "F"?
+
+The result of such a bisection would be that we would find that H is
+the first bad commit, when in fact it's B. So that would be wrong!
+
+And yes it's can happen in practice that people working on one branch
+are not aware that people working on another branch fixed a bug! It
+could also happen that F fixed more than one bug or that it is a
+revert of some big development effort that was not ready to be
+released.
+
+In fact development teams often maintain both a development branch and
+a maintenance branch, and it would be quite easy for them if "git
+bisect" just worked when they want to bisect a regression on the
+development branch that is not on the maintenance branch. They should
+be able to start bisecting using:
+
+-------------
+$ git bisect start dev main
+-------------
+
+To enable that additional nice feature, when a bisection is started
+and when some good commits are not ancestors of the bad commit, we
+first compute the merge bases between the bad and the good commits and
+we chose these merge bases as the first commits that will be checked
+out and tested.
+
+If it happens that one merge base is bad, then the bisection process
+is stopped with a message like:
+
+-------------
+The merge base BBBBBB is bad.
+This means the bug has been fixed between BBBBBB and [GGGGGG,...].
+-------------
+
+where BBBBBB is the sha1 hash of the bad merge base and [GGGGGG,...]
+is a comma separated list of the sha1 of the good commits.
+
+If some of the merge bases are skipped, then the bisection process
+continues, but the following message is printed for each skipped merge
+base:
+
+-------------
+Warning: the merge base between BBBBBB and [GGGGGG,...] must be skipped.
+So we cannot be sure the first bad commit is between MMMMMM and BBBBBB.
+We continue anyway.
+-------------
+
+where BBBBBB is the sha1 hash of the bad commit, MMMMMM is the sha1
+hash of the merge base that is skipped and [GGGGGG,...] is a comma
+separated list of the sha1 of the good commits.
+
+So if there is no bad merge base, the bisection process continues as
+usual after this step.
+
+Best bisecting practices
+------------------------
+
+Using test suites and git bisect together
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If you both have a test suite and use git bisect, then it becomes less
+important to check that all tests pass after each commit. Though of
+course it is probably a good idea to have some checks to avoid
+breaking too many things because it could make bisecting other bugs
+more difficult.
+
+You can focus your efforts to check at a few points (for example rc
+and beta releases) that all the T test cases pass for all the N
+configurations. And when some tests don't pass you can use "git
+bisect" (or better "git bisect run"). So you should perform roughly:
+
+-------------
+c * N * T + b * M * log2(M) tests
+-------------
+
+where c is the number of rounds of test (so a small constant) and b is
+the ratio of bug per commit (hopefully a small constant too).
+
+So of course it's much better as it's O(N \* T) vs O(N \* T \* M) if
+you would test everything after each commit.
+
+This means that test suites are good to prevent some bugs from being
+committed and they are also quite good to tell you that you have some
+bugs. But they are not so good to tell you where some bugs have been
+introduced. To tell you that efficiently, git bisect is needed.
+
+The other nice thing with test suites, is that when you have one, you
+already know how to test for bad behavior. So you can use this
+knowledge to create a new test case for "git bisect" when it appears
+that there is a regression. So it will be easier to bisect the bug and
+fix it. And then you can add the test case you just created to your
+test suite.
+
+So if you know how to create test cases and how to bisect, you will be
+subject to a virtuous circle:
+
+more tests => easier to create tests => easier to bisect => more tests
+
+So test suites and "git bisect" are complementary tools that are very
+powerful and efficient when used together.
+
+Bisecting build failures
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+You can very easily automatically bisect broken builds using something
+like:
+
+-------------
+$ git bisect start BAD GOOD
+$ git bisect run make
+-------------
+
+Passing sh -c "some commands" to "git bisect run"
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+For example:
+
+-------------
+$ git bisect run sh -c "make || exit 125; ./my_app | grep 'good output'"
+-------------
+
+On the other hand if you do this often, then it can be worth having
+scripts to avoid too much typing.
+
+Finding performance regressions
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Here is an example script that comes slightly modified from a real
+world script used by Junio Hamano <<4>>.
+
+This script can be passed to "git bisect run" to find the commit that
+introduced a performance regression:
+
+-------------
+#!/bin/sh
+
+# Build errors are not what I am interested in.
+make my_app || exit 255
+
+# We are checking if it stops in a reasonable amount of time, so
+# let it run in the background...
+
+./my_app >log 2>&1 &
+
+# ... and grab its process ID.
+pid=$!
+
+# ... and then wait for sufficiently long.
+sleep $NORMAL_TIME
+
+# ... and then see if the process is still there.
+if kill -0 $pid
+then
+ # It is still running -- that is bad.
+ kill $pid; sleep 1; kill $pid;
+ exit 1
+else
+ # It has already finished (the $pid process was no more),
+ # and we are happy.
+ exit 0
+fi
+-------------
+
+Following general best practices
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+It is obviously a good idea not to have commits with changes that
+knowingly break things, even if some other commits later fix the
+breakage.
+
+It is also a good idea when using any VCS to have only one small
+logical change in each commit.
+
+The smaller the changes in your commit, the most effective "git
+bisect" will be. And you will probably need "git bisect" less in the
+first place, as small changes are easier to review even if they are
+only reviewed by the commiter.
+
+Another good idea is to have good commit messages. They can be very
+helpful to understand why some changes were made.
+
+These general best practices are very helpful if you bisect often.
+
+Avoiding bug prone merges
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+First merges by themselves can introduce some regressions even when
+the merge needs no source code conflict resolution. This is because a
+semantic change can happen in one branch while the other branch is not
+aware of it.
+
+For example one branch can change the semantic of a function while the
+other branch add more calls to the same function.
+
+This is made much worse if many files have to be fixed to resolve
+conflicts. That's why such merges are called "evil merges". They can
+make regressions very difficult to track down. It can even be
+misleading to know the first bad commit if it happens to be such a
+merge, because people might think that the bug comes from bad conflict
+resolution when it comes from a semantic change in one branch.
+
+Anyway "git rebase" can be used to linearize history. This can be used
+either to avoid merging in the first place. Or it can be used to
+bisect on a linear history instead of the non linear one, as this
+should give more information in case of a semantic change in one
+branch.
+
+Merges can be also made simpler by using smaller branches or by using
+many topic branches instead of only long version related branches.
+
+And testing can be done more often in special integration branches
+like linux-next for the linux kernel.
+
+Adapting your work-flow
+~~~~~~~~~~~~~~~~~~~~~~~
+
+A special work-flow to process regressions can give great results.
+
+Here is an example of a work-flow used by Andreas Ericsson:
+
+* write, in the test suite, a test script that exposes the regression
+* use "git bisect run" to find the commit that introduced it
+* fix the bug that is often made obvious by the previous step
+* commit both the fix and the test script (and if needed more tests)
+
+And here is what Andreas said about this work-flow <<5>>:
+
+_____________
+To give some hard figures, we used to have an average report-to-fix
+cycle of 142.6 hours (according to our somewhat weird bug-tracker
+which just measures wall-clock time). Since we moved to git, we've
+lowered that to 16.2 hours. Primarily because we can stay on top of
+the bug fixing now, and because everyone's jockeying to get to fix
+bugs (we're quite proud of how lazy we are to let git find the bugs
+for us). Each new release results in ~40% fewer bugs (almost certainly
+due to how we now feel about writing tests).
+_____________
+
+Clearly this work-flow uses the virtuous circle between test suites
+and "git bisect". In fact it makes it the standard procedure to deal
+with regression.
+
+In other messages Andreas says that they also use the "best practices"
+described above: small logical commits, topic branches, no evil
+merge,... These practices all improve the bisectability of the commit
+graph, by making it easier and more useful to bisect.
+
+So a good work-flow should be designed around the above points. That
+is making bisecting easier, more useful and standard.
+
+Involving QA people and if possible end users
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+One nice about "git bisect" is that it is not only a developer
+tool. It can effectively be used by QA people or even end users (if
+they have access to the source code or if they can get access to all
+the builds).
+
+There was a discussion at one point on the linux kernel mailing list
+of whether it was ok to always ask end user to bisect, and very good
+points were made to support the point of view that it is ok.
+
+For example David Miller wrote <<6>>:
+
+_____________
+What people don't get is that this is a situation where the "end node
+principle" applies. When you have limited resources (here: developers)
+you don't push the bulk of the burden upon them. Instead you push
+things out to the resource you have a lot of, the end nodes (here:
+users), so that the situation actually scales.
+_____________
+
+This means that it is often "cheaper" if QA people or end users can do
+it.
+
+What is interesting too is that end users that are reporting bugs (or
+QA people that reproduced a bug) have access to the environment where
+the bug happens. So they can often more easily reproduce a
+regression. And if they can bisect, then more information will be
+extracted from the environment where the bug happens, which means that
+it will be easier to understand and then fix the bug.
+
+For open source projects it can be a good way to get more useful
+contributions from end users, and to introduce them to QA and
+development activities.
+
+Using complex scripts
+~~~~~~~~~~~~~~~~~~~~~
+
+In some cases like for kernel development it can be worth developing
+complex scripts to be able to fully automate bisecting.
+
+Here is what Ingo Molnar says about that <<7>>:
+
+_____________
+i have a fully automated bootup-hang bisection script. It is based on
+"git-bisect run". I run the script, it builds and boots kernels fully
+automatically, and when the bootup fails (the script notices that via
+the serial log, which it continuously watches - or via a timeout, if
+the system does not come up within 10 minutes it's a "bad" kernel),
+the script raises my attention via a beep and i power cycle the test
+box. (yeah, i should make use of a managed power outlet to 100%
+automate it)
+_____________
+
+Combining test suites, git bisect and other systems together
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+We have seen that test suites an git bisect are very powerful when
+used together. It can be even more powerful if you can combine them
+with other systems.
+
+For example some test suites could be run automatically at night with
+some unusual (or even random) configurations. And if a regression is
+found by a test suite, then "git bisect" can be automatically
+launched, and its result can be emailed to the author of the first bad
+commit found by "git bisect", and perhaps other people too. And a new
+entry in the bug tracking system could be automatically created too.
+
+
+The future of bisecting
+-----------------------
+
+"git replace"
+~~~~~~~~~~~~~
+
+We saw earlier that "git bisect skip" is now using a PRNG to try to
+avoid areas in the commit graph where commits are untestable. The
+problem is that sometimes the first bad commit will be in an
+untestable area.
+
+To simplify the discussion we will suppose that the untestable area is
+a simple string of commits and that it was created by a breakage
+introduced by one commit (let's call it BBC for bisect breaking
+commit) and later fixed by another one (let's call it BFC for bisect
+fixing commit).
+
+For example:
+
+-------------
+...-Y-BBC-X1-X2-X3-X4-X5-X6-BFC-Z-...
+-------------
+
+where we know that Y is good and BFC is bad, and where BBC and X1 to
+X6 are untestable.
+
+In this case if you are bisecting manually, what you can do is create
+a special branch that starts just before the BBC. The first commit in
+this branch should be the BBC with the BFC squashed into it. And the
+other commits in the branch should be the commits between BBC and BFC
+rebased on the first commit of the branch and then the commit after
+BFC also rebased on.
+
+For example:
+
+-------------
+ (BBC+BFC)-X1'-X2'-X3'-X4'-X5'-X6'-Z'
+ /
+...-Y-BBC-X1-X2-X3-X4-X5-X6-BFC-Z-...
+-------------
+
+where commits quoted with ' have been rebased.
+
+You can easily create such a branch with Git using interactive rebase.
+
+For example using:
+
+-------------
+$ git rebase -i Y Z
+-------------
+
+and then moving BFC after BBC and squashing it.
+
+After that you can start bisecting as usual in the new branch and you
+should eventually find the first bad commit.
+
+For example:
+
+-------------
+$ git bisect start Z' Y
+-------------
+
+If you are using "git bisect run", you can use the same manual fix up
+as above, and then start another "git bisect run" in the special
+branch. Or as the "git bisect" man page says, the script passed to
+"git bisect run" can apply a patch before it compiles and test the
+software <<8>>. The patch should turn a current untestable commits
+into a testable one. So the testing will result in "good" or "bad" and
+"git bisect" will be able to find the first bad commit. And the script
+should not forget to remove the patch once the testing is done before
+exiting from the script.
+
+(Note that instead of a patch you can use "git cherry-pick BFC" to
+apply the fix, and in this case you should use "git reset --hard
+HEAD^" to revert the cherry-pick after testing and before returning
+from the script.)
+
+But the above ways to work around untestable areas are a little bit
+clunky. Using special branches is nice because these branches can be
+shared by developers like usual branches, but the risk is that people
+will get many such branches. And it disrupts the normal "git bisect"
+work-flow. So, if you want to use "git bisect run" completely
+automatically, you have to add special code in your script to restart
+bisection in the special branches.
+
+Anyway one can notice in the above special branch example that the Z'
+and Z commits should point to the same source code state (the same
+"tree" in git parlance). That's because Z' result from applying the
+same changes as Z just in a slightly different order.
+
+So if we could just "replace" Z by Z' when we bisect, then we would
+not need to add anything to a script. It would just work for anyone in
+the project sharing the special branches and the replacements.
+
+With the example above that would give:
+
+-------------
+ (BBC+BFC)-X1'-X2'-X3'-X4'-X5'-X6'-Z'-...
+ /
+...-Y-BBC-X1-X2-X3-X4-X5-X6-BFC-Z
+-------------
+
+That's why the "git replace" command was created. Technically it
+stores replacements "refs" in the "refs/replace/" hierarchy. These
+"refs" are like branches (that are stored in "refs/heads/") or tags
+(that are stored in "refs/tags"), and that means that they can
+automatically be shared like branches or tags among developers.
+
+"git replace" is a very powerful mechanism. It can be used to fix
+commits in already released history, for example to change the commit
+message or the author. And it can also be used instead of git "grafts"
+to link a repository with another old repository.
+
+In fact it's this last feature that "sold" it to the git community, so
+it is now in the "master" branch of git's git repository and it should
+be released in git 1.6.5 in October or November 2009.
+
+One problem with "git replace" is that currently it stores all the
+replacements refs in "refs/replace/", but it would be perhaps better
+if the replacement refs that are useful only for bisecting would be in
+"refs/replace/bisect/". This way the replacement refs could be used
+only for bisecting, while other refs directly in "refs/replace/" would
+be used nearly all the time.
+
+Bisecting sporadic bugs
+~~~~~~~~~~~~~~~~~~~~~~~
+
+Another possible improvement to "git bisect" would be to optionally
+add some redundancy to the tests performed so that it would be more
+reliable when tracking sporadic bugs.
+
+This has been requested by some kernel developers because some bugs
+called sporadic bugs do not appear in all the kernel builds because
+they are very dependent on the compiler output.
+
+The idea is that every 3 test for example, "git bisect" could ask the
+user to test a commit that has already been found to be "good" or
+"bad" (because one of its descendants or one of its ancestors has been
+found to be "good" or "bad" respectively). If it happens that a commit
+has been previously incorrectly classified then the bisection can be
+aborted early, hopefully before too many mistakes have been made. Then
+the user will have to look at what happened and then restart the
+bisection using a fixed bisect log.
+
+There is already a project called BBChop created by Ealdwulf Wuffinga
+on Github that does something like that using Bayesian Search Theory
+<<9>>:
+
+_____________
+BBChop is like 'git bisect' (or equivalent), but works when your bug
+is intermittent. That is, it works in the presence of false negatives
+(when a version happens to work this time even though it contains the
+bug). It assumes that there are no false positives (in principle, the
+same approach would work, but adding it may be non-trivial).
+_____________
+
+But BBChop is independent of any VCS and it would be easier for Git
+users to have something integrated in Git.
+
+Conclusion
+----------
+
+We have seen that regressions are an important problem, and that "git
+bisect" has nice features that complement very well practices and
+other tools, especially test suites, that are generally used to fight
+regressions. But it might be needed to change some work-flows and
+(bad) habits to get the most out of it.
+
+Some improvements to the algorithms inside "git bisect" are possible
+and some new features could help in some cases, but overall "git
+bisect" works already very well, is used a lot, and is already very
+useful. To back up that last claim, let's give the final word to Ingo
+Molnar when he was asked by the author how much time does he think
+"git bisect" saves him when he uses it:
+
+_____________
+a _lot_.
+
+About ten years ago did i do my first 'bisection' of a Linux patch
+queue. That was prior the Git (and even prior the BitKeeper) days. I
+literally days spent sorting out patches, creating what in essence
+were standalone commits that i guessed to be related to that bug.
+
+It was a tool of absolute last resort. I'd rather spend days looking
+at printk output than do a manual 'patch bisection'.
+
+With Git bisect it's a breeze: in the best case i can get a ~15 step
+kernel bisection done in 20-30 minutes, in an automated way. Even with
+manual help or when bisecting multiple, overlapping bugs, it's rarely
+more than an hour.
+
+In fact it's invaluable because there are bugs i would never even
+_try_ to debug if it wasn't for git bisect. In the past there were bug
+patterns that were immediately hopeless for me to debug - at best i
+could send the crash/bug signature to lkml and hope that someone else
+can think of something.
+
+And even if a bisection fails today it tells us something valuable
+about the bug: that it's non-deterministic - timing or kernel image
+layout dependent.
+
+So git bisect is unconditional goodness - and feel free to quote that
+;-)
+_____________
+
+Acknowledgements
+----------------
+
+Many thanks to Junio Hamano for his help in reviewing this paper, for
+reviewing the patches I sent to the git mailing list, for discussing
+some ideas and helping me improve them, for improving "git bisect" a
+lot and for his awesome work in maintaining and developing Git.
+
+Many thanks to Ingo Molnar for giving me very useful information that
+appears in this paper, for commenting on this paper, for his
+suggestions to improve "git bisect" and for evangelizing "git bisect"
+on the linux kernel mailing lists.
+
+Many thanks to Linus Torvalds for inventing, developing and
+evangelizing "git bisect", Git and Linux.
+
+Many thanks to the many other great people who helped one way or
+another when I worked on git, especially to Andreas Ericsson, Johannes
+Schindelin, H. Peter Anvin, Daniel Barkalow, Bill Lear, John Hawley,
+Shawn O. Pierce, Jeff King, Sam Vilain, Jon Seymour.
+
+Many thanks to the Linux-Kongress program committee for choosing the
+author to given a talk and for publishing this paper.
+
+References
+----------
+
+- [[[1]]] http://www.nist.gov/public_affairs/releases/n02-10.htm['Software Errors Cost U.S. Economy $59.5 Billion Annually'. Nist News Release.]
+- [[[2]]] http://java.sun.com/docs/codeconv/html/CodeConventions.doc.html#16712['Code Conventions for the Java Programming Language'. Sun Microsystems.]
+- [[[3]]] http://en.wikipedia.org/wiki/Software_maintenance['Software maintenance'. Wikipedia.]
+- [[[4]]] http://article.gmane.org/gmane.comp.version-control.git/45195/[Junio C Hamano. 'Automated bisect success story'. Gmane.]
+- [[[5]]] http://lwn.net/Articles/317154/[Christian Couder. 'Fully automated bisecting with "git bisect run"'. LWN.net.]
+- [[[6]]] http://lwn.net/Articles/277872/[Jonathan Corbet. 'Bisection divides users and developers'. LWN.net.]
+- [[[7]]] http://article.gmane.org/gmane.linux.scsi/36652/[Ingo Molnar. 'Re: BUG 2.6.23-rc3 can't see sd partitions on Alpha'. Gmane.]
+- [[[8]]] http://www.kernel.org/pub/software/scm/git/docs/git-bisect.html[Junio C Hamano and the git-list. 'git-bisect(1) Manual Page'. Linux Kernel Archives.]
+- [[[9]]] http://github.com/Ealdwulf/bbchop[Ealdwulf. 'bbchop'. GitHub.]
diff --git a/Documentation/git-bisect.txt b/Documentation/git-bisect.txt
index d2ffae0c10..c39d957c3a 100644
--- a/Documentation/git-bisect.txt
+++ b/Documentation/git-bisect.txt
@@ -330,6 +330,11 @@ Documentation
-------------
Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
+SEE ALSO
+--------
+link:git-bisect-lk2009.html[Fighting regressions with git bisect],
+linkgit:git-blame[1].
+
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt
index 3ea80c820f..d227cec9ba 100644
--- a/Documentation/git-commit.txt
+++ b/Documentation/git-commit.txt
@@ -9,7 +9,7 @@ SYNOPSIS
--------
[verse]
'git commit' [-a | --interactive] [-s] [-v] [-u<mode>] [--amend] [--dry-run]
- [(-c | -C) <commit>] [-F <file> | -m <msg>]
+ [(-c | -C) <commit>] [-F <file> | -m <msg>] [--reset-author]
[--allow-empty] [--no-verify] [-e] [--author=<author>]
[--cleanup=<mode>] [--] [[-i | -o ]<file>...]
@@ -69,6 +69,11 @@ OPTIONS
Like '-C', but with '-c' the editor is invoked, so that
the user can further edit the commit message.
+--reset-author::
+ When used with -C/-c/--amend options, declare that the
+ authorship of the resulting commit now belongs of the committer.
+ This also renews the author timestamp.
+
-F <file>::
--file=<file>::
Take the commit message from the given file. Use '-' to
diff --git a/Documentation/git-mailinfo.txt b/Documentation/git-mailinfo.txt
index 996c3fcc6c..b81ac98cf0 100644
--- a/Documentation/git-mailinfo.txt
+++ b/Documentation/git-mailinfo.txt
@@ -8,7 +8,7 @@ git-mailinfo - Extracts patch and authorship from a single e-mail message
SYNOPSIS
--------
-'git mailinfo' [-k] [-u | --encoding=<encoding> | -n] [--scissors] <msg> <patch>
+'git mailinfo' [-k|-b] [-u | --encoding=<encoding> | -n] [--scissors] <msg> <patch>
DESCRIPTION
@@ -32,6 +32,11 @@ OPTIONS
munging, and is most useful when used to read back
'git-format-patch -k' output.
+-b::
+ When -k is not in effect, all leading strings bracketed with '['
+ and ']' pairs are stripped. This option limits the stripping to
+ only the pairs whose bracketed string contains the word "PATCH".
+
-u::
The commit log message, author name and author email are
taken from the e-mail, and after minimally decoding MIME
diff --git a/Documentation/git-send-email.txt b/Documentation/git-send-email.txt
index c85d7f4385..8c482f40b9 100644
--- a/Documentation/git-send-email.txt
+++ b/Documentation/git-send-email.txt
@@ -108,9 +108,10 @@ Sending
--envelope-sender=<address>::
Specify the envelope sender used to send the emails.
This is useful if your default address is not the address that is
- subscribed to a list. If you use the sendmail binary, you must have
- suitable privileges for the -f parameter. Default is the value of
- the 'sendemail.envelopesender' configuration variable; if that is
+ subscribed to a list. In order to use the 'From' address, set the
+ value to "auto". If you use the sendmail binary, you must have
+ suitable privileges for the -f parameter. Default is the value of the
+ 'sendemail.envelopesender' configuration variable; if that is
unspecified, choosing the envelope sender is left to your MTA.
--smtp-encryption=<encryption>::
diff --git a/Documentation/pretty-formats.txt b/Documentation/pretty-formats.txt
index 0683fb3a3d..53a9168ba7 100644
--- a/Documentation/pretty-formats.txt
+++ b/Documentation/pretty-formats.txt
@@ -144,6 +144,14 @@ insert an empty string unless we are traversing reflog entries (e.g., by
`git log -g`). The `%d` placeholder will use the "short" decoration
format if `--decorate` was not already provided on the command line.
+If you add a `{plus}` (plus sign) after '%' of a placeholder, a line-feed
+is inserted immediately before the expansion if and only if the
+placeholder expands to a non-empty string.
+
+If you add a `-` (minus sign) after '%' of a placeholder, line-feeds that
+immediately precede the expansion are deleted if and only if the
+placeholder expands to an empty string.
+
* 'tformat:'
+
The 'tformat:' format works exactly like 'format:', except that it
diff --git a/Makefile b/Makefile
index 4dba10e7f0..4a1e5bcc4d 100644
--- a/Makefile
+++ b/Makefile
@@ -205,6 +205,9 @@ all::
#
# Define NO_REGEX if you have no or inferior regex support in your C library.
#
+# Define JSMIN to point to JavaScript minifier that functions as
+# a filter to have gitweb.js minified.
+#
# Define DEFAULT_PAGER to a sensible pager command (defaults to "less") if
# you want to use something different. The value will be interpreted by the
# shell at runtime when it is used.
@@ -274,6 +277,9 @@ lib = lib
# DESTDIR=
pathsep = :
+# JavaScript minifier invocation that can function as filter
+JSMIN =
+
# default configuration for gitweb
GITWEB_CONFIG = gitweb_config.perl
GITWEB_CONFIG_SYSTEM = /etc/gitweb.conf
@@ -289,6 +295,11 @@ GITWEB_HOMETEXT = indextext.html
GITWEB_CSS = gitweb.css
GITWEB_LOGO = git-logo.png
GITWEB_FAVICON = git-favicon.png
+ifdef JSMIN
+GITWEB_JS = gitweb.min.js
+else
+GITWEB_JS = gitweb.js
+endif
GITWEB_SITE_HEADER =
GITWEB_SITE_FOOTER =
@@ -1498,8 +1509,13 @@ $(patsubst %.perl,%,$(SCRIPT_PERL)): % : %.perl
chmod +x $@+ && \
mv $@+ $@
+ifdef JSMIN
+OTHER_PROGRAMS += gitweb/gitweb.cgi gitweb/gitweb.min.js
+gitweb/gitweb.cgi: gitweb/gitweb.perl gitweb/gitweb.min.js
+else
OTHER_PROGRAMS += gitweb/gitweb.cgi
gitweb/gitweb.cgi: gitweb/gitweb.perl
+endif
$(QUIET_GEN)$(RM) $@ $@+ && \
sed -e '1s|#!.*perl|#!$(PERL_PATH_SQ)|' \
-e 's|++GIT_VERSION++|$(GIT_VERSION)|g' \
@@ -1518,13 +1534,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' \
@@ -1533,6 +1550,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 $@+ && \
@@ -1547,6 +1566,11 @@ $(patsubst %.perl,%,$(SCRIPT_PERL)) git-instaweb: % : unimplemented.sh
mv $@+ $@
endif # NO_PERL
+ifdef JSMIN
+gitweb/gitweb.min.js: gitweb/gitweb.js
+ $(QUIET_GEN)$(JSMIN) <$< >$@
+endif # JSMIN
+
configure: configure.ac
$(QUIET_GEN)$(RM) $@ $<+ && \
sed -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
diff --git a/builtin-commit.c b/builtin-commit.c
index 09d28405ec..e93a647c59 100644
--- a/builtin-commit.c
+++ b/builtin-commit.c
@@ -51,7 +51,7 @@ static const char *template_file;
static char *edit_message, *use_message;
static char *author_name, *author_email, *author_date;
static int all, edit_flag, also, interactive, only, amend, signoff;
-static int quiet, verbose, no_verify, allow_empty, dry_run;
+static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;
static char *untracked_files_arg;
/*
* The default commit message cleanup mode will remove the lines
@@ -91,8 +91,9 @@ static struct option builtin_commit_options[] = {
OPT_FILENAME('F', "file", &logfile, "read log from file"),
OPT_STRING(0, "author", &force_author, "AUTHOR", "override author for commit"),
OPT_CALLBACK('m', "message", &message, "MESSAGE", "specify commit message", opt_parse_m),
- OPT_STRING('c', "reedit-message", &edit_message, "COMMIT", "reuse and edit message from specified commit "),
+ OPT_STRING('c', "reedit-message", &edit_message, "COMMIT", "reuse and edit message from specified commit"),
OPT_STRING('C', "reuse-message", &use_message, "COMMIT", "reuse message from specified commit"),
+ OPT_BOOLEAN(0, "reset-author", &renew_authorship, "the commit is authored by me now (used with -C-c/--amend)"),
OPT_BOOLEAN('s', "signoff", &signoff, "add Signed-off-by:"),
OPT_FILENAME('t', "template", &template_file, "use specified template file"),
OPT_BOOLEAN('e', "edit", &edit_flag, "force edit of commit"),
@@ -381,7 +382,7 @@ static void determine_author_info(void)
email = getenv("GIT_AUTHOR_EMAIL");
date = getenv("GIT_AUTHOR_DATE");
- if (use_message) {
+ if (use_message && !renew_authorship) {
const char *a, *lb, *rb, *eol;
a = strstr(use_message_buffer, "\nauthor ");
@@ -747,6 +748,9 @@ static int parse_and_validate_options(int argc, const char *argv[],
if (force_author && !strchr(force_author, '>'))
force_author = find_author_by_nickname(force_author);
+ if (force_author && renew_authorship)
+ die("Using both --reset-author and --author does not make sense");
+
if (logfile || message.len || use_message)
use_editor = 0;
if (edit_flag)
@@ -780,6 +784,8 @@ static int parse_and_validate_options(int argc, const char *argv[],
use_message = edit_message;
if (amend && !use_message)
use_message = "HEAD";
+ if (!use_message && renew_authorship)
+ die("--reset-author can be used only with -C, -c or --amend.");
if (use_message) {
unsigned char sha1[20];
static char utf8[] = "UTF-8";
diff --git a/builtin-log.c b/builtin-log.c
index 33fa6ea6c8..1766349550 100644
--- a/builtin-log.c
+++ b/builtin-log.c
@@ -976,7 +976,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
*/
argc = parse_options(argc, argv, prefix, builtin_format_patch_options,
builtin_format_patch_usage,
- PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN);
+ PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN |
+ PARSE_OPT_KEEP_DASHDASH);
if (do_signoff) {
const char *committer;
diff --git a/builtin-mailinfo.c b/builtin-mailinfo.c
index 3c4f0753fe..a50ac2256c 100644
--- a/builtin-mailinfo.c
+++ b/builtin-mailinfo.c
@@ -10,6 +10,7 @@
static FILE *cmitmsg, *patchfile, *fin, *fout;
static int keep_subject;
+static int keep_non_patch_brackets_in_subject;
static const char *metainfo_charset;
static struct strbuf line = STRBUF_INIT;
static struct strbuf name = STRBUF_INIT;
@@ -221,35 +222,41 @@ static int is_multipart_boundary(const struct strbuf *line)
static void cleanup_subject(struct strbuf *subject)
{
- char *pos;
- size_t remove;
- while (subject->len) {
- switch (*subject->buf) {
+ size_t at = 0;
+
+ while (at < subject->len) {
+ char *pos;
+ size_t remove;
+
+ switch (subject->buf[at]) {
case 'r': case 'R':
- if (subject->len <= 3)
+ if (subject->len <= at + 3)
break;
- if (!memcmp(subject->buf + 1, "e:", 2)) {
- strbuf_remove(subject, 0, 3);
+ if (!memcmp(subject->buf + at + 1, "e:", 2)) {
+ strbuf_remove(subject, at, 3);
continue;
}
+ at++;
break;
case ' ': case '\t': case ':':
- strbuf_remove(subject, 0, 1);
+ strbuf_remove(subject, at, 1);
continue;
case '[':
- if ((pos = strchr(subject->buf, ']'))) {
- remove = pos - subject->buf;
- if (remove <= (subject->len - remove) * 2) {
- strbuf_remove(subject, 0, remove + 1);
- continue;
- }
- } else
- strbuf_remove(subject, 0, 1);
- break;
+ pos = strchr(subject->buf + at, ']');
+ if (!pos)
+ break;
+ remove = pos - subject->buf + at + 1;
+ if (!keep_non_patch_brackets_in_subject ||
+ (7 <= remove &&
+ memmem(subject->buf + at, remove, "PATCH", 5)))
+ strbuf_remove(subject, at, remove);
+ else
+ at += remove;
+ continue;
}
- strbuf_trim(subject);
- return;
+ break;
}
+ strbuf_trim(subject);
}
static void cleanup_space(struct strbuf *sb)
@@ -1014,7 +1021,7 @@ static int git_mailinfo_config(const char *var, const char *value, void *unused)
}
static const char mailinfo_usage[] =
- "git mailinfo [-k] [-u | --encoding=<encoding> | -n] [--scissors | --no-scissors] msg patch < mail >info";
+ "git mailinfo [-k|-b] [-u | --encoding=<encoding> | -n] [--scissors | --no-scissors] msg patch < mail >info";
int cmd_mailinfo(int argc, const char **argv, const char *prefix)
{
@@ -1031,6 +1038,8 @@ int cmd_mailinfo(int argc, const char **argv, const char *prefix)
while (1 < argc && argv[1][0] == '-') {
if (!strcmp(argv[1], "-k"))
keep_subject = 1;
+ else if (!strcmp(argv[1], "-b"))
+ keep_non_patch_brackets_in_subject = 1;
else if (!strcmp(argv[1], "-u"))
metainfo_charset = def_charset;
else if (!strcmp(argv[1], "-n"))
diff --git a/builtin-merge.c b/builtin-merge.c
index 57eedd447d..fc7066e3c7 100644
--- a/builtin-merge.c
+++ b/builtin-merge.c
@@ -107,8 +107,8 @@ static struct strategy *get_strategy(const char *name)
found = 1;
if (!found)
add_cmdname(&not_strategies, ent->name, ent->len);
- exclude_cmds(&main_cmds, &not_strategies);
}
+ exclude_cmds(&main_cmds, &not_strategies);
}
if (!is_in_cmdlist(&main_cmds, name) && !is_in_cmdlist(&other_cmds, name)) {
fprintf(stderr, "Could not find merge strategy '%s'.\n", name);
@@ -656,6 +656,7 @@ static int checkout_fast_forward(unsigned char *head, unsigned char *remote)
opts.verbose_update = 1;
opts.merge = 1;
opts.fn = twoway_merge;
+ opts.msgs = get_porcelain_error_msgs();
trees[nr_trees] = parse_tree_indirect(head);
if (!trees[nr_trees++])
diff --git a/builtin-shortlog.c b/builtin-shortlog.c
index 8aa63c7857..b3b055f68c 100644
--- a/builtin-shortlog.c
+++ b/builtin-shortlog.c
@@ -139,8 +139,12 @@ static void read_from_stdin(struct shortlog *log)
void shortlog_add_commit(struct shortlog *log, struct commit *commit)
{
const char *author = NULL, *buffer;
+ struct strbuf buf = STRBUF_INIT;
+ struct strbuf ufbuf = STRBUF_INIT;
+ struct pretty_print_context ctx = {0};
- buffer = commit->buffer;
+ pretty_print_commit(CMIT_FMT_RAW, commit, &buf, &ctx);
+ buffer = buf.buf;
while (*buffer && *buffer != '\n') {
const char *eol = strchr(buffer, '\n');
@@ -157,20 +161,19 @@ void shortlog_add_commit(struct shortlog *log, struct commit *commit)
die("Missing author: %s",
sha1_to_hex(commit->object.sha1));
if (log->user_format) {
- struct strbuf buf = STRBUF_INIT;
struct pretty_print_context ctx = {0};
ctx.abbrev = DEFAULT_ABBREV;
ctx.subject = "";
ctx.after_subject = "";
ctx.date_mode = DATE_NORMAL;
- pretty_print_commit(CMIT_FMT_USERFORMAT, commit, &buf, &ctx);
- insert_one_record(log, author, buf.buf);
- strbuf_release(&buf);
- return;
- }
- if (*buffer)
+ pretty_print_commit(CMIT_FMT_USERFORMAT, commit, &ufbuf, &ctx);
+ buffer = ufbuf.buf;
+ } else if (*buffer) {
buffer++;
+ }
insert_one_record(log, author, !*buffer ? "<none>" : buffer);
+ strbuf_release(&ufbuf);
+ strbuf_release(&buf);
}
static void get_from_rev(struct rev_info *rev, struct shortlog *log)
diff --git a/combine-diff.c b/combine-diff.c
index 5b63af1eeb..61626912e3 100644
--- a/combine-diff.c
+++ b/combine-diff.c
@@ -524,6 +524,7 @@ static void dump_sline(struct sline *sline, unsigned long cnt, int num_parent,
int i;
unsigned long lno = 0;
const char *c_frag = diff_get_color(use_color, DIFF_FRAGINFO);
+ const char *c_func = diff_get_color(use_color, DIFF_FUNCINFO);
const char *c_new = diff_get_color(use_color, DIFF_FILE_NEW);
const char *c_old = diff_get_color(use_color, DIFF_FILE_OLD);
const char *c_plain = diff_get_color(use_color, DIFF_PLAIN);
@@ -588,7 +589,9 @@ static void dump_sline(struct sline *sline, unsigned long cnt, int num_parent,
comment_end = i;
}
if (comment_end)
- putchar(' ');
+ printf("%s%s %s%s", c_reset,
+ c_plain, c_reset,
+ c_func);
for (i = 0; i < comment_end; i++)
putchar(hunk_comment[i]);
}
diff --git a/diff.c b/diff.c
index 0d7f5ea4a8..d952686926 100644
--- a/diff.c
+++ b/diff.c
@@ -39,6 +39,7 @@ static char diff_colors[][COLOR_MAXLEN] = {
GIT_COLOR_GREEN, /* NEW */
GIT_COLOR_YELLOW, /* COMMIT */
GIT_COLOR_BG_RED, /* WHITESPACE */
+ GIT_COLOR_NORMAL, /* FUNCINFO */
};
static void diff_filespec_load_driver(struct diff_filespec *one);
@@ -60,6 +61,8 @@ static int parse_diff_color_slot(const char *var, int ofs)
return DIFF_COMMIT;
if (!strcasecmp(var+ofs, "whitespace"))
return DIFF_WHITESPACE;
+ if (!strcasecmp(var+ofs, "func"))
+ return DIFF_FUNCINFO;
die("bad config variable '%s'", var);
}
@@ -295,12 +298,13 @@ static void emit_line_0(FILE *file, const char *set, const char *reset,
nofirst = 0;
}
- fputs(set, file);
-
- if (!nofirst)
- fputc(first, file);
- fwrite(line, len, 1, file);
- fputs(reset, file);
+ if (len || !nofirst) {
+ fputs(set, file);
+ if (!nofirst)
+ fputc(first, file);
+ fwrite(line, len, 1, file);
+ fputs(reset, file);
+ }
if (has_trailing_carriage_return)
fputc('\r', file);
if (has_trailing_newline)
@@ -344,6 +348,42 @@ static void emit_add_line(const char *reset,
}
}
+static void emit_hunk_header(struct emit_callback *ecbdata,
+ const char *line, int len)
+{
+ const char *plain = diff_get_color(ecbdata->color_diff, DIFF_PLAIN);
+ const char *frag = diff_get_color(ecbdata->color_diff, DIFF_FRAGINFO);
+ const char *func = diff_get_color(ecbdata->color_diff, DIFF_FUNCINFO);
+ const char *reset = diff_get_color(ecbdata->color_diff, DIFF_RESET);
+ static const char atat[2] = { '@', '@' };
+ const char *cp, *ep;
+
+ /*
+ * As a hunk header must begin with "@@ -<old>, +<new> @@",
+ * it always is at least 10 bytes long.
+ */
+ if (len < 10 ||
+ memcmp(line, atat, 2) ||
+ !(ep = memmem(line + 2, len - 2, atat, 2))) {
+ emit_line(ecbdata->file, plain, reset, line, len);
+ return;
+ }
+ ep += 2; /* skip over @@ */
+
+ /* The hunk header in fraginfo color */
+ emit_line(ecbdata->file, frag, reset, line, ep - line);
+
+ /* blank before the func header */
+ for (cp = ep; ep - line < len; ep++)
+ if (*ep != ' ' && *ep != '\t')
+ break;
+ if (ep != cp)
+ emit_line(ecbdata->file, plain, reset, cp, ep - cp);
+
+ if (ep < line + len)
+ emit_line(ecbdata->file, func, reset, ep, line + len - ep);
+}
+
static struct diff_tempfile *claim_diff_tempfile(void) {
int i;
for (i = 0; i < ARRAY_SIZE(diff_temp); i++)
@@ -781,9 +821,7 @@ static void fn_out_consume(void *priv, char *line, unsigned long len)
diff_words_flush(ecbdata);
len = sane_truncate_line(ecbdata, line, len);
find_lno(line, ecbdata);
- emit_line(ecbdata->file,
- diff_get_color(ecbdata->color_diff, DIFF_FRAGINFO),
- reset, line, len);
+ emit_hunk_header(ecbdata, line, len);
if (line[len-1] != '\n')
putc('\n', ecbdata->file);
return;
diff --git a/diff.h b/diff.h
index 2740421cfe..15fcecdecd 100644
--- a/diff.h
+++ b/diff.h
@@ -130,6 +130,7 @@ enum color_diff {
DIFF_FILE_NEW = 5,
DIFF_COMMIT = 6,
DIFF_WHITESPACE = 7,
+ DIFF_FUNCINFO = 8,
};
const char *diff_get_color(int diff_use_color, enum color_diff ix);
#define diff_get_color_opt(o, ix) \
diff --git a/git-am.sh b/git-am.sh
index 512891d217..4838cdb9ed 100755
--- a/git-am.sh
+++ b/git-am.sh
@@ -577,11 +577,12 @@ do
git cat-file commit "$commit" |
sed -e '1,/^$/d' >"$dotest/msg-clean"
else
- SUBJECT="$(sed -n '/^Subject/ s/Subject: //p' "$dotest/info")"
- case "$keep_subject" in -k) SUBJECT="[PATCH] $SUBJECT" ;; esac
-
- (printf '%s\n\n' "$SUBJECT"; cat "$dotest/msg") |
- git stripspace > "$dotest/msg-clean"
+ {
+ sed -n '/^Subject/ s/Subject: //p' "$dotest/info"
+ echo
+ cat "$dotest/msg"
+ } |
+ git stripspace > "$dotest/msg-clean"
fi
;;
esac
diff --git a/git-instaweb.sh b/git-instaweb.sh
index 341930ca9d..b8e6456208 100755
--- a/git-instaweb.sh
+++ b/git-instaweb.sh
@@ -394,8 +394,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/git-send-email.perl b/git-send-email.perl
index 4f5da4ecf2..319b535671 100755
--- a/git-send-email.perl
+++ b/git-send-email.perl
@@ -187,9 +187,11 @@ my ($identity, $aliasfiletype, @alias_files, @smtp_host_parts);
my ($validate, $confirm);
my (@suppress_cc);
+my $not_set_by_user = "true but not set by the user";
+
my %config_bool_settings = (
"thread" => [\$thread, 1],
- "chainreplyto" => [\$chain_reply_to, 1],
+ "chainreplyto" => [\$chain_reply_to, $not_set_by_user],
"suppressfrom" => [\$suppress_from, undef],
"signedoffbycc" => [\$signed_off_by_cc, undef],
"signedoffcc" => [\$signed_off_by_cc, undef], # Deprecated
@@ -214,6 +216,19 @@ my %config_settings = (
"from" => \$sender,
);
+# Help users prepare for 1.7.0
+sub chain_reply_to {
+ if (defined $chain_reply_to &&
+ $chain_reply_to eq $not_set_by_user) {
+ print STDERR
+ "In git 1.7.0, the default will be changed to --no-chain-reply-to\n" .
+ "Set sendemail.chainreplyto configuration variable to true if\n" .
+ "you want to keep --chain-reply-to as your default.\n";
+ $chain_reply_to = 1;
+ }
+ return $chain_reply_to;
+}
+
# Handle Uncouth Termination
sub signal_handler {
@@ -862,7 +877,9 @@ X-Mailer: git-send-email $gitversion
my @sendmail_parameters = ('-i', @recipients);
my $raw_from = $sanitized_sender;
- $raw_from = $envelope_sender if (defined $envelope_sender);
+ if (defined $envelope_sender && $envelope_sender ne "auto") {
+ $raw_from = $envelope_sender;
+ }
$raw_from = extract_valid_address($raw_from);
unshift (@sendmail_parameters,
'-f', $raw_from) if(defined $envelope_sender);
@@ -1157,7 +1174,7 @@ foreach my $t (@files) {
# set up for the next message
if ($thread && $message_was_sent &&
- ($chain_reply_to || !defined $reply_to || length($reply_to) == 0)) {
+ (chain_reply_to() || !defined $reply_to || length($reply_to) == 0)) {
$reply_to = $message_id;
if (length $references > 0) {
$references .= "\n $message_id";
diff --git a/gitweb/README b/gitweb/README
index 66c6a9391d..b69b0e5042 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 cb3f0baf05..50067f2e0d 100644
--- a/gitweb/gitweb.css
+++ b/gitweb/gitweb.css
@@ -79,6 +79,13 @@ div.page_footer_text {
font-style: italic;
}
+div#generating_info {
+ margin: 4px;
+ font-size: smaller;
+ text-align: center;
+ color: #505050;
+}
+
div.page_body {
padding: 8px;
font-family: monospace;
@@ -254,6 +261,11 @@ tr.no-previous td.linenr {
font-weight: bold;
}
+/* for 'blame_incremental', during processing */
+tr.color1 { background-color: #f6fff6; }
+tr.color2 { background-color: #f6f6ff; }
+tr.color3 { background-color: #fff6f6; }
+
td {
padding: 2px 5px;
font-size: 100%;
@@ -345,6 +357,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;
+}
+
/* format of (optional) objects size in 'tree' view */
td.size {
font-family: monospace;
diff --git a/gitweb/gitweb.js b/gitweb/gitweb.js
new file mode 100644
index 0000000000..2a25b7cc47
--- /dev/null
+++ b/gitweb/gitweb.js
@@ -0,0 +1,870 @@
+// 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
+ */
+
+/* ============================================================ */
+/* functions for generic gitweb actions and views */
+
+/**
+ * used to check if link has 'js' query parameter already (at end),
+ * and other reasons to not add 'js=1' param at the end of link
+ * @constant
+ */
+var jsExceptionsRe = /[;?]js=[01]$/;
+
+/**
+ * Add '?js=1' or ';js=1' to the end of every link in the document
+ * that doesn't have 'js' query parameter set already.
+ *
+ * Links with 'js=1' lead to JavaScript version of given action, if it
+ * exists (currently there is only 'blame_incremental' for 'blame')
+ *
+ * @globals jsExceptionsRe
+ */
+function fixLinks() {
+ var allLinks = document.getElementsByTagName("a") || document.links;
+ for (var i = 0, len = allLinks.length; i < len; i++) {
+ var link = allLinks[i];
+ if (!jsExceptionsRe.test(link)) { // =~ /[;?]js=[01]$/;
+ link.href +=
+ (link.href.indexOf('?') === -1 ? '?' : ';') + 'js=1';
+ }
+ }
+}
+
+
+/* ============================================================ */
+
+/*
+ * 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, '\u00A0') == '\u00A012'
+ * ('\u00A0' 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. '\u00A0'
+ * @returns {String} INPUT prefixed with (WIDTH - INPUT.length) x STR
+ */
+function padLeftStr(input, width, str) {
+ var prefix = '';
+
+ width -= input.toString().length;
+ while (width > 0) {
+ 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, '\u00A0') + '%)';
+ }
+
+ 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 during blame_data (git blame --incremental) run */
+
+/**
+ * used to extract N from 'colorN', where N is a number,
+ * @constant
+ */
+var colorRe = /\bcolor([0-9]*)\b/;
+
+/**
+ * return N if <tr class="colorN">, otherwise return null
+ * (some browsers require CSS class names to begin with letter)
+ *
+ * @param {HTMLElement} tr: table row element to check
+ * @param {String} tr.className: 'class' attribute of tr element
+ * @returns {Number|null} N if tr.className == 'colorN', otherwise null
+ *
+ * @globals colorRe
+ */
+function getColorNo(tr) {
+ if (!tr) {
+ return null;
+ }
+ var className = tr.className;
+ if (className) {
+ var match = colorRe.exec(className);
+ if (match) {
+ return parseInt(match[1], 10);
+ }
+ }
+ return null;
+}
+
+var colorsFreq = [0, 0, 0];
+/**
+ * return one of given possible colors (curently least used one)
+ * example: chooseColorNoFrom(2, 3) returns 2 or 3
+ *
+ * @param {Number[]} arguments: one or more numbers
+ * assumes that 1 <= arguments[i] <= colorsFreq.length
+ * @returns {Number} Least used color number from arguments
+ * @globals colorsFreq
+ */
+function chooseColorNoFrom() {
+ // choose the color which is least used
+ var colorNo = arguments[0];
+ for (var i = 1; i < arguments.length; i++) {
+ if (colorsFreq[arguments[i]-1] < colorsFreq[colorNo-1]) {
+ colorNo = arguments[i];
+ }
+ }
+ colorsFreq[colorNo-1]++;
+ return colorNo;
+}
+
+/**
+ * given two neigbour <tr> elements, find color which would be different
+ * from color of both of neighbours; used to 3-color blame table
+ *
+ * @param {HTMLElement} tr_prev
+ * @param {HTMLElement} tr_next
+ * @returns {Number} color number N such that
+ * colorN != tr_prev.className && colorN != tr_next.className
+ */
+function findColorNo(tr_prev, tr_next) {
+ var color_prev = getColorNo(tr_prev);
+ var color_next = getColorNo(tr_next);
+
+
+ // neither of neighbours has color set
+ // THEN we can use any of 3 possible colors
+ if (!color_prev && !color_next) {
+ return chooseColorNoFrom(1,2,3);
+ }
+
+ // either both neighbours have the same color,
+ // or only one of neighbours have color set
+ // THEN we can use any color except given
+ var color;
+ if (color_prev === color_next) {
+ color = color_prev; // = color_next;
+ } else if (!color_prev) {
+ color = color_next;
+ } else if (!color_next) {
+ color = color_prev;
+ }
+ if (color) {
+ return chooseColorNoFrom((color % 3) + 1, ((color+1) % 3) + 1);
+ }
+
+ // neighbours have different colors
+ // THEN there is only one color left
+ return (3 - ((color_prev + color_next) % 3));
+}
+
+/* ............................................................ */
+/* 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';
+}
+
+/**
+ * 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);
+ }
+
+ // color depends on group of lines, not only on blamed commit
+ var colorNo = findColorNo(
+ document.getElementById('l'+(resline-1)),
+ document.getElementById('l'+(resline+group.numlines))
+ );
+
+ // 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 = '';
+ if (colorNo !== null) {
+ tr_class = 'color'+colorNo;
+ }
+ 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;
+ if (a_sha1.firstChild) {
+ a_sha1.firstChild.data = commit.sha1.substr(0, 8);
+ } else {
+ a_sha1.appendChild(
+ document.createTextNode(commit.sha1.substr(0, 8)));
+ }
+ if (group.numlines >= 2) {
+ var fragment = document.createDocumentFragment();
+ var br = document.createElement("br");
+ var match = commit.author.match(/\b([A-Z])\B/g);
+ if (match) {
+ var text = document.createTextNode(
+ match.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 f94536c680..7e477af956 100755
--- a/gitweb/gitweb.perl
+++ b/gitweb/gitweb.perl
@@ -18,6 +18,12 @@ use File::Find qw();
use File::Basename qw(basename);
binmode STDOUT, ':utf8';
+our $t0;
+if (eval { require Time::HiRes; 1; }) {
+ $t0 = [Time::HiRes::gettimeofday()];
+}
+our $number_of_git_cmds = 0;
+
BEGIN {
CGI->compile() if $ENV{'MOD_PERL'};
}
@@ -90,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/";
@@ -417,6 +425,20 @@ our %feature = (
'sub' => \&feature_avatar,
'override' => 0,
'default' => ['']},
+
+ # Enable displaying how much time and how many git commands
+ # it took to generate and display page. Disabled by default.
+ # Project specific override is not supported.
+ 'timed' => {
+ 'override' => 0,
+ 'default' => [0]},
+
+ # Enable turning some links into links to actions which require
+ # JavaScript to run (like 'blame_incremental'). Not enabled by
+ # default. Project specific override is currently not supported.
+ 'javascript-actions' => {
+ 'override' => 0,
+ 'default' => [0]},
);
sub gitweb_get_feature {
@@ -531,6 +553,7 @@ if (-e $GITWEB_CONFIG) {
# version of the core git binary
our $git_version = qx("$GIT" --version) =~ m/git version (.*)$/ ? $1 : "unknown";
+$number_of_git_cmds++;
$projects_list ||= $projectroot;
@@ -568,12 +591,16 @@ our @cgi_param_mapping = (
snapshot_format => "sf",
extra_options => "opt",
search_use_regexp => "sr",
+ # this must be last entry (for manipulation from JavaScript)
+ javascript => "js"
);
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,
@@ -2006,6 +2033,7 @@ sub get_feed_info {
# returns path to the core git executable and the --git-dir parameter as list
sub git_cmd {
+ $number_of_git_cmds++;
return $GIT, '--git-dir='.$git_dir;
}
@@ -3281,10 +3309,36 @@ sub git_footer_html {
}
print "</div>\n"; # class="page_footer"
+ if (defined $t0 && gitweb_check_feature('timed')) {
+ print "<div id=\"generating_info\">\n";
+ print 'This page took '.
+ '<span id="generating_time" class="time_span">'.
+ Time::HiRes::tv_interval($t0, [Time::HiRes::gettimeofday()]).
+ ' seconds </span>'.
+ ' and '.
+ '<span id="generating_cmd">'.
+ $number_of_git_cmds.
+ '</span> git commands '.
+ " to generate.\n";
+ print "</div>\n"; # class="page_footer"
+ }
+
if (-f $site_footer) {
insert_file($site_footer);
}
+ print qq!<script type="text/javascript" src="$javascript"></script>\n!;
+ if ($action eq 'blame_incremental') {
+ print qq!<script type="text/javascript">\n!.
+ qq!startBlame("!. href(action=>"blame_data", -replay=>1) .qq!",\n!.
+ qq! "!. href() .qq!");\n!.
+ qq!</script>\n!;
+ } elsif (gitweb_check_feature('javascript-actions')) {
+ print qq!<script type="text/javascript">\n!.
+ qq!window.onload = fixLinks;\n!.
+ qq!</script>\n!;
+ }
+
print "</body>\n" .
"</html>";
}
@@ -4881,7 +4935,13 @@ sub git_tag {
git_footer_html();
}
-sub git_blame {
+sub git_blame_common {
+ my $format = shift || 'porcelain';
+ if ($format eq 'porcelain' && $cgi->param('js')) {
+ $format = 'incremental';
+ $action = 'blame_incremental'; # for page title etc
+ }
+
# permissions
gitweb_check_feature('blame')
or die_error(403, "Blame view not allowed");
@@ -4903,123 +4963,220 @@ 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();
my $formats_nav =
$cgi->a({-href => href(action=>"blob", -replay=>1)},
"blob") .
+ " | ";
+ if ($format eq 'incremental') {
+ $formats_nav .=
+ $cgi->a({-href => href(action=>"blame", javascript=>0, -replay=>1)},
+ "blame") . " (non-incremental)";
+ } else {
+ $formats_nav .=
+ $cgi->a({-href => href(action=>"blame_incremental", -replay=>1)},
+ "blame") . " (incremental)";
+ }
+ $formats_nav .=
" | " .
$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',javascript=>0,-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
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();
diff --git a/merge-recursive.c b/merge-recursive.c
index a91208f295..dd4fbd0e6b 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -172,23 +172,6 @@ static int git_merge_trees(int index_only,
int rc;
struct tree_desc t[3];
struct unpack_trees_options opts;
- struct unpack_trees_error_msgs msgs = {
- /* would_overwrite */
- "Your local changes to '%s' would be overwritten by merge. Aborting.",
- /* not_uptodate_file */
- "Your local changes to '%s' would be overwritten by merge. Aborting.",
- /* not_uptodate_dir */
- "Updating '%s' would lose untracked files in it. Aborting.",
- /* would_lose_untracked */
- "Untracked working tree file '%s' would be %s by merge. Aborting",
- /* bind_overlap -- will not happen here */
- NULL,
- };
- if (advice_commit_before_merge) {
- msgs.would_overwrite = msgs.not_uptodate_file =
- "Your local changes to '%s' would be overwritten by merge. Aborting.\n"
- "Please, commit your changes or stash them before you can merge.";
- }
memset(&opts, 0, sizeof(opts));
if (index_only)
@@ -200,7 +183,7 @@ static int git_merge_trees(int index_only,
opts.fn = threeway_merge;
opts.src_index = &the_index;
opts.dst_index = &the_index;
- opts.msgs = msgs;
+ opts.msgs = get_porcelain_error_msgs();
init_tree_desc_from_tree(t+0, common);
init_tree_desc_from_tree(t+1, head);
@@ -1188,6 +1171,28 @@ static int process_entry(struct merge_options *o,
return clean_merge;
}
+struct unpack_trees_error_msgs get_porcelain_error_msgs(void)
+{
+ struct unpack_trees_error_msgs msgs = {
+ /* would_overwrite */
+ "Your local changes to '%s' would be overwritten by merge. Aborting.",
+ /* not_uptodate_file */
+ "Your local changes to '%s' would be overwritten by merge. Aborting.",
+ /* not_uptodate_dir */
+ "Updating '%s' would lose untracked files in it. Aborting.",
+ /* would_lose_untracked */
+ "Untracked working tree file '%s' would be %s by merge. Aborting",
+ /* bind_overlap -- will not happen here */
+ NULL,
+ };
+ if (advice_commit_before_merge) {
+ msgs.would_overwrite = msgs.not_uptodate_file =
+ "Your local changes to '%s' would be overwritten by merge. Aborting.\n"
+ "Please, commit your changes or stash them before you can merge.";
+ }
+ return msgs;
+}
+
int merge_trees(struct merge_options *o,
struct tree *head,
struct tree *merge,
diff --git a/merge-recursive.h b/merge-recursive.h
index fd138ca140..d8bc7299ee 100644
--- a/merge-recursive.h
+++ b/merge-recursive.h
@@ -17,6 +17,9 @@ struct merge_options {
struct string_list current_directory_set;
};
+/* Return a list of user-friendly error messages to be used by merge */
+struct unpack_trees_error_msgs get_porcelain_error_msgs(void);
+
/* merge_trees() but with recursive ancestor consolidation */
int merge_recursive(struct merge_options *o,
struct commit *h1,
diff --git a/pretty.c b/pretty.c
index 5661cba595..8f5bd1ab7f 100644
--- a/pretty.c
+++ b/pretty.c
@@ -628,8 +628,8 @@ static void rewrap_message_tail(struct strbuf *sb,
c->indent2 = new_indent2;
}
-static size_t format_commit_item(struct strbuf *sb, const char *placeholder,
- void *context)
+static size_t format_commit_one(struct strbuf *sb, const char *placeholder,
+ void *context)
{
struct format_commit_context *c = context;
const struct commit *commit = c->commit;
@@ -816,6 +816,44 @@ static size_t format_commit_item(struct strbuf *sb, const char *placeholder,
return 0; /* unknown placeholder */
}
+static size_t format_commit_item(struct strbuf *sb, const char *placeholder,
+ void *context)
+{
+ int consumed;
+ size_t orig_len;
+ enum {
+ NO_MAGIC,
+ ADD_LF_BEFORE_NON_EMPTY,
+ DEL_LF_BEFORE_EMPTY,
+ } magic = NO_MAGIC;
+
+ switch (placeholder[0]) {
+ case '-':
+ magic = DEL_LF_BEFORE_EMPTY;
+ break;
+ case '+':
+ magic = ADD_LF_BEFORE_NON_EMPTY;
+ break;
+ default:
+ break;
+ }
+ if (magic != NO_MAGIC)
+ placeholder++;
+
+ orig_len = sb->len;
+ consumed = format_commit_one(sb, placeholder, context);
+ if (magic == NO_MAGIC)
+ return consumed;
+
+ if ((orig_len == sb->len) && magic == DEL_LF_BEFORE_EMPTY) {
+ while (sb->len && sb->buf[sb->len - 1] == '\n')
+ strbuf_setlen(sb, sb->len - 1);
+ } else if ((orig_len != sb->len) && magic == ADD_LF_BEFORE_NON_EMPTY) {
+ strbuf_insert(sb, orig_len, "\n", 1);
+ }
+ return consumed + 1;
+}
+
void format_commit_message(const struct commit *commit,
const char *format, struct strbuf *sb,
const struct pretty_print_context *pretty_ctx)
diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh
index 7f267f9ed1..3bc1cccf88 100755
--- a/t/t4014-format-patch.sh
+++ b/t/t4014-format-patch.sh
@@ -552,4 +552,9 @@ test_expect_success 'format-patch --numstat should produce a patch' '
git format-patch --numstat --stdout master..side > output &&
test 6 = $(grep "^diff --git a/" output | wc -l)'
+test_expect_success 'format-patch -- <path>' '
+ git format-patch master..side -- file 2>error &&
+ ! grep "Use .--" error
+'
+
test_done
diff --git a/t/t4034-diff-words.sh b/t/t4034-diff-words.sh
index 21db6e95c4..1c21276c55 100755
--- a/t/t4034-diff-words.sh
+++ b/t/t4034-diff-words.sh
@@ -8,6 +8,7 @@ test_expect_success setup '
git config diff.color.old red
git config diff.color.new green
+ git config diff.color.func magenta
'
@@ -16,6 +17,7 @@ decrypt_color () {
-e 's/.\[1m/<WHITE>/g' \
-e 's/.\[31m/<RED>/g' \
-e 's/.\[32m/<GREEN>/g' \
+ -e 's/.\[35m/<MAGENTA>/g' \
-e 's/.\[36m/<BROWN>/g' \
-e 's/.\[m/<RESET>/g'
}
@@ -49,7 +51,7 @@ cat > expect <<\EOF
<WHITE>+++ b/post<RESET>
<BROWN>@@ -1,3 +1,7 @@<RESET>
<RED>h(4)<RESET><GREEN>h(4),hh[44]<RESET>
-<RESET>
+
a = b + c<RESET>
<GREEN>aa = a<RESET>
@@ -70,7 +72,7 @@ cat > expect <<\EOF
<WHITE>+++ b/post<RESET>
<BROWN>@@ -1 +1 @@<RESET>
<RED>h(4)<RESET><GREEN>h(4),hh[44]<RESET>
-<BROWN>@@ -3,0 +4,4 @@ a = b + c<RESET>
+<BROWN>@@ -3,0 +4,4 @@<RESET> <RESET><MAGENTA>a = b + c<RESET>
<GREEN>aa = a<RESET>
@@ -90,7 +92,7 @@ cat > expect <<\EOF
<WHITE>+++ b/post<RESET>
<BROWN>@@ -1,3 +1,7 @@<RESET>
h(4),<GREEN>hh<RESET>[44]
-<RESET>
+
a = b + c<RESET>
<GREEN>aa = a<RESET>
@@ -126,7 +128,7 @@ cat > expect <<\EOF
<WHITE>+++ b/post<RESET>
<BROWN>@@ -1,3 +1,7 @@<RESET>
h(4)<GREEN>,hh[44]<RESET>
-<RESET>
+
a = b + c<RESET>
<GREEN>aa = a<RESET>
@@ -168,7 +170,7 @@ cat > expect <<\EOF
<WHITE>+++ b/post<RESET>
<BROWN>@@ -1,3 +1,7 @@<RESET>
h(4),<GREEN>hh[44<RESET>]
-<RESET>
+
a = b + c<RESET>
<GREEN>aa = a<RESET>
diff --git a/t/t4201-shortlog.sh b/t/t4201-shortlog.sh
index 405b971191..dd818f6fd6 100755
--- a/t/t4201-shortlog.sh
+++ b/t/t4201-shortlog.sh
@@ -52,4 +52,32 @@ GIT_DIR=non-existing git shortlog -w < log > out
test_expect_success 'shortlog from non-git directory' 'test_cmp expect out'
+iconvfromutf8toiso88591() {
+ printf "%s" "$*" | iconv -f UTF-8 -t ISO-8859-1
+}
+
+DSCHO="Jöhännës \"Dschö\" Schindëlin"
+DSCHOE="$DSCHO <Johannes.Schindelin@gmx.de>"
+MSG1="set a1 to 2 and some non-ASCII chars: Äßø"
+MSG2="set a1 to 3 and some non-ASCII chars: áæï"
+cat > expect << EOF
+$DSCHO (2):
+ $MSG1
+ $MSG2
+
+EOF
+
+test_expect_success 'shortlog encoding' '
+ git reset --hard "$commit" &&
+ git config --unset i18n.commitencoding &&
+ echo 2 > a1 &&
+ git commit --quiet -m "$MSG1" --author="$DSCHOE" a1 &&
+ git config i18n.commitencoding "ISO-8859-1" &&
+ echo 3 > a1 &&
+ git commit --quiet -m "$(iconvfromutf8toiso88591 "$MSG2")" \
+ --author="$(iconvfromutf8toiso88591 "$DSCHOE")" a1 &&
+ git config --unset i18n.commitencoding &&
+ git shortlog HEAD~2.. > out &&
+test_cmp expect out'
+
test_done
diff --git a/t/t6006-rev-list-format.sh b/t/t6006-rev-list-format.sh
index 7f61ab0e52..571931588e 100755
--- a/t/t6006-rev-list-format.sh
+++ b/t/t6006-rev-list-format.sh
@@ -162,6 +162,28 @@ test_expect_success 'empty email' '
}
'
+test_expect_success 'del LF before empty (1)' '
+ git show -s --pretty=format:"%s%n%-b%nThanks%n" HEAD^^ >actual &&
+ test $(wc -l <actual) = 2
+'
+
+test_expect_success 'del LF before empty (2)' '
+ git show -s --pretty=format:"%s%n%-b%nThanks%n" HEAD >actual &&
+ test $(wc -l <actual) = 6 &&
+ grep "^$" actual
+'
+
+test_expect_success 'add LF before non-empty (1)' '
+ git show -s --pretty=format:"%s%+b%nThanks%n" HEAD^^ >actual &&
+ test $(wc -l <actual) = 2
+'
+
+test_expect_success 'add LF before non-empty (2)' '
+ git show -s --pretty=format:"%s%+b%nThanks%n" HEAD >actual &&
+ test $(wc -l <actual) = 6 &&
+ grep "^$" actual
+'
+
test_expect_success '"%h %gD: %gs" is same as git-reflog' '
git reflog >expect &&
git log -g --format="%h %gD: %gs" >actual &&
diff --git a/t/t7509-commit.sh b/t/t7509-commit.sh
new file mode 100755
index 0000000000..d52c060b06
--- /dev/null
+++ b/t/t7509-commit.sh
@@ -0,0 +1,114 @@
+#!/bin/sh
+#
+# Copyright (c) 2009 Erick Mattos
+#
+
+test_description='git commit --reset-author'
+
+. ./test-lib.sh
+
+author_header () {
+ git cat-file commit "$1" |
+ sed -n -e '/^$/q' -e '/^author /p'
+}
+
+message_body () {
+ git cat-file commit "$1" |
+ sed -e '1,/^$/d'
+}
+
+test_expect_success '-C option copies authorship and message' '
+ echo "Initial" >foo &&
+ git add foo &&
+ test_tick &&
+ git commit -m "Initial Commit" --author Frigate\ \<flying@over.world\> &&
+ git tag Initial &&
+ echo "Test 1" >>foo &&
+ test_tick &&
+ git commit -a -C Initial &&
+ author_header Initial >expect &&
+ author_header HEAD >actual &&
+ test_cmp expect actual &&
+
+ message_body Initial >expect &&
+ message_body HEAD >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success '-C option copies only the message with --reset-author' '
+ echo "Test 2" >>foo &&
+ test_tick &&
+ git commit -a -C Initial --reset-author &&
+ echo "author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE" >expect &&
+ author_header HEAD >actual
+ test_cmp expect actual &&
+
+ message_body Initial >expect &&
+ message_body HEAD >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success '-c option copies authorship and message' '
+ echo "Test 3" >>foo &&
+ test_tick &&
+ EDITOR=: VISUAL=: git commit -a -c Initial &&
+ author_header Initial >expect &&
+ author_header HEAD >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success '-c option copies only the message with --reset-author' '
+ echo "Test 4" >>foo &&
+ test_tick &&
+ EDITOR=: VISUAL=: git commit -a -c Initial --reset-author &&
+ echo "author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE" >expect &&
+ author_header HEAD >actual &&
+ test_cmp expect actual &&
+
+ message_body Initial >expect &&
+ message_body HEAD >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success '--amend option copies authorship' '
+ git checkout Initial &&
+ echo "Test 5" >>foo &&
+ test_tick &&
+ git commit -a --amend -m "amend test" &&
+ author_header Initial >expect &&
+ author_header HEAD >actual &&
+
+ echo "amend test" >expect &&
+ message_body HEAD >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success '--reset-author makes the commit ours even with --amend option' '
+ git checkout Initial &&
+ echo "Test 6" >>foo &&
+ test_tick &&
+ git commit -a --reset-author -m "Changed again" --amend &&
+ echo "author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE" >expect &&
+ author_header HEAD >actual &&
+ test_cmp expect actual &&
+
+ echo "Changed again" >expect &&
+ message_body HEAD >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success '--reset-author and --author are mutually exclusive' '
+ git checkout Initial &&
+ echo "Test 7" >>foo &&
+ test_tick &&
+ test_must_fail git commit -a --reset-author --author="Xyzzy <frotz@nitfol.xz>"
+'
+
+test_expect_success '--reset-author should be rejected without -c/-C/--amend' '
+ git checkout Initial &&
+ echo "Test 7" >>foo &&
+ test_tick &&
+ test_must_fail git commit -a --reset-author -m done
+'
+
+test_done
diff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh
index 84a7f03d46..fb51ab3dbe 100755
--- a/t/t9001-send-email.sh
+++ b/t/t9001-send-email.sh
@@ -95,6 +95,40 @@ test_expect_success \
'Verify commandline' \
'test_cmp expected commandline1'
+test_expect_success 'Send patches with --envelope-sender' '
+ clean_fake_sendmail &&
+ git send-email --envelope-sender="Patch Contributer <patch@example.com>" --suppress-cc=sob --from="Example <nobody@example.com>" --to=nobody@example.com --smtp-server="$(pwd)/fake.sendmail" $patches 2>errors
+'
+
+cat >expected <<\EOF
+!patch@example.com!
+!-i!
+!nobody@example.com!
+!author@example.com!
+!one@example.com!
+!two@example.com!
+EOF
+test_expect_success \
+ 'Verify commandline' \
+ 'test_cmp expected commandline1'
+
+test_expect_success 'Send patches with --envelope-sender=auto' '
+ clean_fake_sendmail &&
+ git send-email --envelope-sender=auto --suppress-cc=sob --from="Example <nobody@example.com>" --to=nobody@example.com --smtp-server="$(pwd)/fake.sendmail" $patches 2>errors
+'
+
+cat >expected <<\EOF
+!nobody@example.com!
+!-i!
+!nobody@example.com!
+!author@example.com!
+!one@example.com!
+!two@example.com!
+EOF
+test_expect_success \
+ 'Verify commandline' \
+ 'test_cmp expected commandline1'
+
cat >expected-show-all-headers <<\EOF
0001-Second.patch
(mbox) Adding cc: A <author@example.com> from line 'From: A <author@example.com>'
@@ -769,4 +803,53 @@ test_expect_success 'threading but no chain-reply-to' '
grep "In-Reply-To: " stdout
'
+test_expect_success 'warning with an implicit --chain-reply-to' '
+ git send-email \
+ --dry-run \
+ --from="Example <nobody@example.com>" \
+ --to=nobody@example.com \
+ outdir/000?-*.patch 2>errors >out &&
+ grep "no-chain-reply-to" errors
+'
+
+test_expect_success 'no warning with an explicit --chain-reply-to' '
+ git send-email \
+ --dry-run \
+ --from="Example <nobody@example.com>" \
+ --to=nobody@example.com \
+ --chain-reply-to \
+ outdir/000?-*.patch 2>errors >out &&
+ ! grep "no-chain-reply-to" errors
+'
+
+test_expect_success 'no warning with an explicit --no-chain-reply-to' '
+ git send-email \
+ --dry-run \
+ --from="Example <nobody@example.com>" \
+ --to=nobody@example.com \
+ --no-chain-reply-to \
+ outdir/000?-*.patch 2>errors >out &&
+ ! grep "no-chain-reply-to" errors
+'
+
+test_expect_success 'no warning with sendemail.chainreplyto = false' '
+ git config sendemail.chainreplyto false &&
+ git send-email \
+ --dry-run \
+ --from="Example <nobody@example.com>" \
+ --to=nobody@example.com \
+ outdir/000?-*.patch 2>errors >out &&
+ ! grep "no-chain-reply-to" errors
+'
+
+test_expect_success 'no warning with sendemail.chainreplyto = true' '
+ git config sendemail.chainreplyto true &&
+ git send-email \
+ --dry-run \
+ --from="Example <nobody@example.com>" \
+ --to=nobody@example.com \
+ outdir/000?-*.patch 2>errors >out &&
+ ! grep "no-chain-reply-to" errors
+'
+
test_done