Git development
 help / color / mirror / Atom feed
* Struggling with tangled
From: Alan Chandler @ 2006-11-22 10:37 UTC (permalink / raw)
  To: Git Mailing List

I am trying to sort out a tangled (in the sense that I several branches that 
split a long time ago, but are reasonably close subsets of each other) 
repository of mine using git rebase.  I want to isolate the commits that 
cause the key differences so that I can then easily enhance the code but 
carry forward the variants (using git-rebase again probably). 

I have some questions which are causing me some grief after merge conflicts. 
Can someone help me. 

1) I often edit a merge conflicted file to the state I expect it to be in at 
the end.  This sometimes means that I edit it to a state where no change is 
seen.  git-update-index notices this and doesn't do anything, but when I try 
git-rebase --continue it won't because it says git-update-index has not been 
run.  What am I supposed to do then? [Is the answer git-rebase --skip ?] 

2) Some files get completely munged with conflict resolution markers every 
few lines.  Is there a simple way to say "don't use this file, but use the 
[stage2/stage3] sources of the merge". (ie one of the original inputs to the 
merge - and if so, which one is which) 

3) I sometime hit a merge conflict in a file which I know will actually be 
deleted at the tip of the topic I am rebasing.  Is there a way at this point 
to just tell the conflict resolution to say make this file go away. 

4) I repeat the question I asked in a thread above.  What is the --merge 
switch on git-rebase actually do.  The man page starts talking about merge 
strategies, but there already is a -s switch for that. 

 --
Alan Chandler
alan@chandlerfamily.org.uk

^ permalink raw reply

* Re: Struggling with tangled
From: Jakub Narebski @ 2006-11-22 11:01 UTC (permalink / raw)
  To: git
In-Reply-To: <E1GmpTj-000235-2n@home.chandlerfamily.org.uk>

Alan Chandler wrote:

> I am trying to sort out a tangled (in the sense that I several branches that 
> split a long time ago, but are reasonably close subsets of each other) 
> repository of mine using git rebase.  I want to isolate the commits that 
> cause the key differences so that I can then easily enhance the code but 
> carry forward the variants (using git-rebase again probably). 
> 
> I have some questions which are causing me some grief after merge conflicts. 
> Can someone help me. 
> 
> 1) I often edit a merge conflicted file to the state I expect it to be in at 
> the end.  This sometimes means that I edit it to a state where no change is 
> seen.  git-update-index notices this and doesn't do anything, but when I try 
> git-rebase --continue it won't because it says git-update-index has not been 
> run.  What am I supposed to do then? [Is the answer git-rebase --skip ?] 

If you resolve conflict to the state where no change is seen, it means that
the commit you currently are rebasing doesn't bring any changes; it was
applied. So you have to do "git rebase --skip".

Sidenote: with git version 1.4.3.4 you cannot "git rebase --skip" while
there are conflict in the index. It is most annoying - I'd like to skip
the resolving. I bring the files in conflict to the "base" version and run
"git update-index" before "git rebase --skip", but I'd like to skip that part.

> 2) Some files get completely munged with conflict resolution markers every 
> few lines.  Is there a simple way to say "don't use this file, but use the 
> [stage2/stage3] sources of the merge". (ie one of the original inputs to the 
> merge - and if so, which one is which) 

"git cat-file -p :<stage>: <filename> > <filename>", where stage = 1 means
version from the ancestor, stage = 2 means version from the HEAD (from the
base), and stage = 3 means version from the remote/other branch (from the
branch being rebased).  

> 3) I sometime hit a merge conflict in a file which I know will actually be 
> deleted at the tip of the topic I am rebasing.  Is there a way at this point 
> to just tell the conflict resolution to say make this file go away. 

"git rm <filename>" plus "git update-index <filename>" doesn't work?

> 4) I repeat the question I asked in a thread above.  What is the --merge 
> switch on git-rebase actually do.  The man page starts talking about merge 
> strategies, but there already is a -s switch for that. 

"git rebase" uses "git format-patch" + "git-am --3way" machinery by default.
The --merge option makes it use merge machinery instead (similar to the way 
"git checkout -m" uses merge strategy IIRC).
-- 
Jakub Narebski
Warsaw, Poland
ShadeHawk on #git


^ permalink raw reply

* [Patch to DRAFT 2 (2/2)] Branching and merging with git
From: Junio C Hamano @ 2006-11-22 11:02 UTC (permalink / raw)
  To: linux; +Cc: git
In-Reply-To: <20061120235136.4841.qmail@science.horizon.com>

This comes on top of the ispell'ed one to correct technical
details:

* We made describe output to be a valid object name some time ago.

* With recent addition to take directory names and path patterns,
  it is not limited to "small number of files" case anymore.

* The original about separate-remote was full of half sentences
  so I stitched them together to make them make some sense.

* Sorry, I recently applied the same "fix" as Cogito got quite
  some time ago, and  both lightweight and annotated tags are
  now followed upon a tracking fetch.

* rsync has been deprecated for quite some time.

* The official party line for git-native-over-ssh is host:path

* Octopus should be discouraged unless talking about truly
  trivial merges.  Explain its downside better.

* Amend is a lot handier than --no-commit, as you do not have to
  plan ahead.  We should encourage "Pull/merge normally and if
  the result is not what you like, amend it" workflow.

--- a/doc
+++ b/doc
@@ -278,11 +278,14 @@
 
 Third is git-describe.  This is something like git-name-rev, but
 backwards: it finds the closest reference that is an ancestor of the
-specified commit.  Its output is not acceptable git input, but takes
-the form of either
+specified commit.  It takes the form of either
+
 	v1.2		(tag name), or
 	v1.2-g12345678	(commit 12345678, whose nearest ancestor is v1.2)
 
+and it is accepted as an input if the abbreviated object name that follows
+"tagname-g" prefix is unambiguous.
+
 git-describe's output is intended to be used as a software version
 number, something like the "rcsid" feature in RCS and CVS.
 By default, git-describe uses only heavyweight tags for its naming,
@@ -345,13 +348,14 @@
    in the working directory.  This option can be used with no <branch>
    specified (defaults to HEAD) to undo local edits.
 
-2) Revert changes to a small number of files.
+2) Revert changes to the files in the working tree.
 
 	git checkout [<revision>] [--] <paths>
    will copy the version of the <paths> from the index to the working
    directory.  If a <revision> is given, the index for those paths will
    be updated from the given revision before copying from the index to
-   the working tree.
+   the working tree.  <paths> can name directories, and/or contain
+   glob patterns to revert many files.
 
    Unlike the version with no <paths> specified, this does NOT change
    HEAD, even if <paths> is ".".
@@ -782,18 +786,12 @@
 default in future), that places copies of the remote servers' heads
 under .git/refs/remotes/<server>/<branch>.
 
-Then you can refer to
-
-If you use "git-line --use-separate-remote", it will set
-
-If you want to use the "separate remotes" tracking branch
-There's an alternate way, using the --use-separate-remote option
-to git-clone.  This sets up a copy of the remote server's heads
-under .git/refs/remotes/origin/<name>.  Then you can refer to
-"origin/<branch>" whenever you want.
+If you use "git-clone --use-separate-remote", it sets up a copy of
+the remote server's heads under .git/refs/remotes/origin/<name>.
+Then you can refer to "origin/<branch>" whenever you want.
 
 Because the branch names are got under .git/refs/heads, the git
-tools will not let you commit to the branch.
+tools will not let you commit to the remote branchbranch.
 
 * Remotes files
 
@@ -877,7 +875,7 @@
 * Remote tags
 
 When you fetch to a tracking branch, git-fetch also fetches every
-heavyweight tag (one that involves an actual tag object) that
+tag under .git/refs/tags/ in the remote repository that
 points to a commit reachable from the branch head and installs a
 copy locally.
 
@@ -913,11 +911,19 @@
 
    http clients also need some extra index information to help them
    find which pack files they need.  git-update-server-info is the
-   command that generates these files, but it's run automatically
-   from git-repack, so it's not too important to know.
+   command that generates these files, and it is important to keep
+   them up-to-date.  A recommended practice is to have this command in
+   .git/hooks/update so that every time you push into the repository
+   they are automatically updated.  You can enable the hook (which
+   is installed when the repository is initialized) with "chmod +x".
+
+   In addition to http://, https:// and ftp:// URL are allowed and
+   handled by the same backend that uses cURL library.
 
 2) rsync protocol.  This is basically an alternative to http, and
-   has the same strictures and limitations.
+   has the same strictures and limitations.  This is deprecated and
+   its use has been discouraged for quite some time, although it still
+   works.
 
 3) The git protocol.  This is represented with a git:// URL, and talks
    to a dedicated git daemon (see the git-daemon man page) on the
@@ -929,8 +935,9 @@
    require any special care when repacking.  git-daemon is purposefully
    written to provide read-only service.
 
-4) The git protocol over ssh.  This is a git+ssh:// URL; ssh:// is
-   accepted as a synonym.  It has the same efficiency issues as
+4) The git protocol over ssh.  This is spelled as "host:path" like scp
+   command, and ssh:// URL and git+ssh:// are
+   accepted as synonyms.  It has the same efficiency issues as
    plain git.  If you want to limit ssh users to just the git commands
    necessary to share work, git provides a git-shell command that can
    be used as a very limited login shell.
@@ -1046,7 +1053,7 @@
 
 
 Now, you can also specify a remote repository to merge from, using a
-git://, http:// or git+ssh:// URL.  This is what Linus does all day
+git:// or  http:// URLs or host:path syntax.  This is what Linus does all day
 long, and why the git-pull tool is optimized to allow that.  It uses
 git-fetch to fetch the remote branch without assigning it a branch name
 (as mentioned above, it gets the magic name FETCH_HEAD), and them merges
@@ -1213,15 +1220,15 @@
 what's already there, enter "5401f304" instead, and click "Goto".)
 
 The octopus strategy is invoked automatically when you specify more
-than one branch at a time to merge in with "git pull".  It can't handle
-complicated overlaps and file renames as well as the 2-way recursive
+than one branch at a time to merge in with "git pull".  It refuses to
+handle complicated overlaps and file renames as well as the 2-way recursive
 strategy, but if you have a number of simple, independent changes that you
 want to merge together, an octopus merge is the obvious way to document
 the fact that they're truly independent.
 
-The only downside to using an octopus to combine a number of simple
-changes is that any merge makes git-bisect's job harder.  If you have
-a development history like
+A major downside to using an octopus to combine a number of 
+changes is that an octopus merge makes git-bisect's job harder.
+If you have a development history like
 
     /-b-\
    /     \
@@ -1234,9 +1241,15 @@
     \-f-/
 
 And you know that a works but g doesn't, there's no way to do a binary
-search on b through f; they have to be searched linearly.  This is
-no harder to bisect, and a lot nicer-looking than the equivalent with
-2-way merges:
+search on b through f; they have to be searched linearly.  In
+addition, a merge tends to become more error prone as it has more
+parents.  Your bisect could show that all of b, c, d, e, f are good
+and the error is in a mismerge at g.  This is why the octopus strategy
+refuses to do anything other than a very simple merge.
+
+Although an octopus is somewhat nicer-looking than the equivalent with
+a series of 2-parent merges, the latter is a lot more efficient to
+bisect:
 
     /-b
    /   \
@@ -1248,7 +1261,12 @@
    \         /
     \-f-----/
 
-But if they were just done one after the other, you'd have
+With this structure, if bisecting at h proves that it was Ok,
+then you do not have to check b, c, d, g (the error must be in e, f
+or mismerge at i or j).
+
+Even simpler to bisect is if they were just done one after the other.
+In such a case, you'd have:
 
 --a--b--c--d--e--f--
 
@@ -1410,6 +1428,9 @@
 
 In many cases, this is fine, and you can save it and complete the
 commit.  Or you can add something about the merge if it needs saying.
+If the merge was complex, it will turn out to be useful to describe
+how you choose to resolve conflicts, and that is the primary reason
+the boilerplate lists conflicted files.
 
 When I'm done, if I don't need branch A any more, I can
 
@@ -1763,11 +1784,11 @@
 the changelog.  It could have been a separate commit, but didn't seem
 worth it.
 
-This was done by forcing the git-pull to not commit:
+This was done by amending the merge commit.
 
-	git pull --no-commit . <branch>
+	git pull . <branch>
 	(edit as desired)
-	git commit
+	git commit --amend -a
 
 The commit message should be edited to explain that this is not just
 a normal merge, as was done in this case.

^ permalink raw reply

* [Patch to DRAFT 2 (1/2)] Branching and merging with git
From: Junio C Hamano @ 2006-11-22 11:02 UTC (permalink / raw)
  To: linux; +Cc: git
In-Reply-To: <20061120235136.4841.qmail@science.horizon.com>

This installment is ispell only.

--- a/doc
+++ b/doc
@@ -81,7 +81,7 @@
 * A brief digression on command names
 
 All git commands can be invoked as "git-foo" and "git foo".  This document
-uses them interchangably.  But you have to ask for the "git-foo" man page.
+uses them interchangeably.  But you have to ask for the "git-foo" man page.
 Git provides a few other ways to get the man page as well:
 	man git-foo
 	git help foo
@@ -120,13 +120,13 @@
 
 Since the wrapper was developed, some simple commands have been made
 "builtin", so for example, "git diff" is done internally.  There's a
-git-diff link to retain compatability.
+git-diff link to retain compatibility.
 
 
 * Git's representation of history
 
 As you recall from Git 101 (or the git(7) man page), git's largest
-data strucure is the object database, holding exactly four kidnds
+data structure is the object database, holding exactly four kinds
 of objects.  Each of them has a globally unique 40-character hex name
 (a.k.a. object IDs) made by hashing its type and contents.  Since this is
 an (effectively unforgeable) cryptographic hash, the name of an object
@@ -141,11 +141,11 @@
 Tree objects record directory contents; they contain file names,
 permissions, and the associated tree or blob object names.
 
-Tag objects are shareable pointers to other objects; they're generally
+Tag objects are sharable pointers to other objects; they're generally
 used to store a digital signature, and generally point to commits.
 (Although you can tag any object, including another tag.)
 
-Finaly, there are commit objects.  Every commit points to (contains the
+Finally, there are commit objects.  Every commit points to (contains the
 name of) an associated tree object which records the state of the source
 code at the time of the commit, and some descriptive data (time, author,
 committer, commit comment) about the commit.
@@ -172,7 +172,7 @@
 common, but git allows many more.  There's a limit of sixteen in
 the source code, and the most anyone's ever used in real life is 12,
 which was generally regarded as overdoing it.  The famous "dodecapus"
-is commit 9fdb62af in the linux kernel repository.
+is commit 9fdb62af in the Linux kernel repository.
 
 
 Finally, there are references, stored in the .git/refs directory.
@@ -184,7 +184,7 @@
 - Tags are references that are intended to be immutable.
   A tag like "v1.2" is a historical record.  Tag references may or may not
   point to tag objects!  If they do, this is called a "heavyweight tag";
-  the tag can hold a digital signature and can be shared between repositores.
+  the tag can hold a digital signature and can be shared between repositories.
   "Lightweight tags" point to commits directly, and are not automatically
   shared.
 - Heads are references that are intended to be updated.
@@ -808,7 +808,7 @@
 it's generally easier to set up a short-cut by placing the options in
 .git/remotes/<name>.
 
-The syntax is explained in the git-fetch man page.  When this is st
+The syntax is explained in the git-fetch man page.  When this is set
 up, "git fetch <name>" will retrieve all the branches listed in the
 .git/remotes/<name> file.  The ability to fetch multiple branches at
 once (such as release, beta, and development) is an advantage of using
@@ -838,7 +838,7 @@
 If you want to watch a project that's hosted on a git server, the easiest
 way is to use "git clone".
 
-git-clone creates a new repository, sets up a remotes file to reack
+git-clone creates a new repository, sets up a remotes file to track
 every branch in the remote repository, and fetches all those branches.
 By default, it maps them to local heads as follows:
 
@@ -868,7 +868,7 @@
 
 Then the fetch will be done, but the results will be written nowhere
 but .git/FETCH_HEAD.  This is actually the earliest form of git_fetch
-impllemented; everything else is a later addition.  It's not something
+implemented; everything else is a later addition.  It's not something
 you'd do on purpose much, except as part of a script that uses FETCH_HEAD,
 but it's worth mentioning it in case you type it and wonder what the
 heck happened.
@@ -920,8 +920,8 @@
    has the same strictures and limitations.
 
 3) The git protocol.  This is represented with a git:// URL, and talks
-   to a dedicated git daemon (see the git-daemin man page) on the
-   remote machine.  It uses TP port 9418 by default.  This is a smart
+   to a dedicated git daemon (see the git-daemon man page) on the
+   remote machine.  It uses TCP port 9418 by default.  This is a smart
    protocol that understands the git format and does sophisticated
    wire compression.
 
@@ -1066,7 +1066,7 @@
 
 That does a git fetch, updating all of the listed branches as usual,
 then merges the _first_ listed branch into HEAD.  It would be more
-cinsistent to merge all the branches, but that's almost never what
+consistent to merge all the branches, but that's almost never what
 you want.
 
 By the way: don't blink, you might miss it!  As I mentioned, pulling is
@@ -1125,7 +1125,7 @@
 In the completely trivial case when O, A and B are the same, then
 all three rules apply, they all produce the same obvious result.
 
-Git automatically finds the masrge base O as the most recent
+Git automatically finds the merge base O as the most recent
 common ancestor of the heads A and B to be merged.
 
 When doing a merge, git uses the above 2-out-of-3 merging rules
@@ -1147,10 +1147,10 @@
 and uses the same 2-out-of-3 rules to resolve each hunk separately.
 
 Only if all three commits have differing hunks that overlap (or come
-so glose that git can't be sure) is git unable to automatically resolve
+so close that git can't be sure) is git unable to automatically resolve
 the problem.  This requires manual correction, as described below.
 
-If the merge goes well, it is automatically comitted and the HEAD branch
+If the merge goes well, it is automatically committed and the HEAD branch
 updated to point to the new commit.
 
 
@@ -1207,7 +1207,7 @@
 
 * Octopus merge (advanced)
 
-The first is the "octopus" stratgy.  This is special because it can do
+The first is the "octopus" strategy.  This is special because it can do
 a three- or more-way merge.  See 5401f304 in the git repository for
 an example.  (Run gitk, double-click on the "SHA1 ID" box to select
 what's already there, enter "5401f304" instead, and click "Goto".)
@@ -1641,7 +1641,7 @@
              \
 	      y--y <-- dev2
 
-Either way, you'll have to manually figure out a list of vommits that
+Either way, you'll have to manually figure out a list of commits that
 you want in dev1 and create that branch:
 
 	git checkout -b dev1 master
@@ -1695,8 +1695,8 @@
 
 * Cherry picking and rebasing: Merging changes
 
-Suppose that you accidentally ommitted a broken source tree,
-and forgot to use "git-commit --amend" when comitting the following
+Suppose that you accidentally committed a broken source tree,
+and forgot to use "git-commit --amend" when committing the following
 one-liner.  Or say you didn't notice the brokenness immediately
 and made the fix a few commits later.  But now you want to
 rewrite history with the fix merged into the original commit.
@@ -1722,7 +1722,7 @@
 Now, we can git-rebase the remainder.  The only complication is
 that git can't tell that the change from commit2 has been applied,
 since the combined 1+2 change isn't "the same".  One option is to
-trust that there will be amerge conflict when you try and just do:
+trust that there will be a merge conflict when you try and just do:
 
 	git checkout dev
 	git rebase --onto temp <commit1>
@@ -1786,10 +1786,10 @@
 Remember that fetching from a repository on the same machine is both
 possible and fast.  So if you want to play around, just make a new
 directory, run git-init-db, and give it a try.  You can't hurt the source
-repoistory, and deleting the destination is as easy as "rm -rf".
+repository, and deleting the destination is as easy as "rm -rf".
 
 (Of course, you can hurt things with "rm -rf", so make certain you're
-in the right directory before excecuting that!)
+in the right directory before executing that!)
 
 * Experimenting with merging
 

^ permalink raw reply

* Re: Struggling with tangled
From: Johannes Schindelin @ 2006-11-22 11:35 UTC (permalink / raw)
  To: Alan Chandler; +Cc: Git Mailing List
In-Reply-To: <E1GmpTj-000235-2n@home.chandlerfamily.org.uk>

Hi,

On Wed, 22 Nov 2006, Alan Chandler wrote:

> 2) Some files get completely munged with conflict resolution markers 
> every few lines.  Is there a simple way to say "don't use this file, but 
> use the [stage2/stage3] sources of the merge". (ie one of the original 
> inputs to the merge - and if so, which one is which)

I find myself using

	git diff --ours <file>

and

	git diff --theirs <file>

in such a case sometimes. If I _know_ my version is good, I do

	git diff --ours <file> | git apply -R

This also updates the index.

Hth,
Dscho

^ permalink raw reply

* Re: [DRAFT] Branching and merging with git
From: Junio C Hamano @ 2006-11-22 11:51 UTC (permalink / raw)
  To: linux; +Cc: git
In-Reply-To: <7vslghftix.fsf@assigned-by-dhcp.cox.net>

Junio C Hamano <junkio@cox.net> writes:

> If you want an esoteric topic for an introductory documentation,
> it would be more useful to talk about evil merges (an evil merge
> is a merge commit whose result does not match any of its
> parents).  A good example is found in
>
> 	git show v1.0.0

I actually remembered a better one.

        Subject: Necessity of "evil" merge and topic branches
        Date: Wed, 17 May 2006 23:25:55 -0700
        Message-ID: <7vy7wz6e8c.fsf@assigned-by-dhcp.cox.net>

This talks about a real-world evil merge and the reason why it
was necessary, and speculates a possible way to make life
easier.  I actually later used the "third branch to remember the
evil merge between two topics" technique I talked about in the
message to merge in another pair of topics, and it turned out
that it worked rather well.

There were two logically independent topics:

 - lt/setup.  Two commits, changing the calling convention of
   setup_git_directory() function -- the final tip of the topic
   was at a633fca0.

 - js/mv.  Three commits, making git-mv a built-in after
   refactoring some code from other parts of the system -- the
   final tip of the topic was at ac64a722).

They were not "obviously correct" when they started, so a topic
branch was used for each.  They had textually and semantically
some conflicts, and if they were to progress at different paces,
there was a need for an evil merge when the later one is merged
to master.

So I created another branch to merge the two topics together and
resolved their conflicts while my reading of their code were
still fresh.

	git checkout -b __/setup-n-mv js/mv
        git pull . lt/setup
	git checkout next
        git pull . __/setup-n-mv

Later js/mv became ready to be merged first.  So I merged it to
'master'.

	git checkout master
        git pull . js/mv

I was planning to cook lt/setup a bit longer but eventually
decided to merge it to 'master' as well after a short while.

	git checkout master
        git pull . __/setup-n-mv

I could have pulled lt/setup into master but then I would have
had to resolve the conflict between the two branches.  Since I
recorded the resolution earlier by making the merge, and pulled
that branch (which contained all of lt/setup already) into
'master', I did not have to remember what I need to adjust when
I did so.  If lt/setup had further updates on its own after the
"third branch __/setup-n-mv" was made, I would have then pulled
the tip of lt/setup into 'master' to complete the merge, and
that would have also resulted in non-conflicting simple merge.

This would have worked equally well if lt/setup were to graduate
first.

This might look too complex at the first look, but I thought it
might be an interesting topic in the "hints for managing your
topic branches" section.

^ permalink raw reply

* Re: Struggling with tangled
From: Junio C Hamano @ 2006-11-22 11:57 UTC (permalink / raw)
  To: Johannes Schindelin; +Cc: git
In-Reply-To: <Pine.LNX.4.63.0611221233370.30004@wbgn013.biozentrum.uni-wuerzburg.de>

Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:

> Hi,
>
> On Wed, 22 Nov 2006, Alan Chandler wrote:
>
>> 2) Some files get completely munged with conflict resolution markers 
>> every few lines.  Is there a simple way to say "don't use this file, but 
>> use the [stage2/stage3] sources of the merge". (ie one of the original 
>> inputs to the merge - and if so, which one is which)
>
> I find myself using
>
> 	git diff --ours <file>
>
> and
>
> 	git diff --theirs <file>
>
> in such a case sometimes. If I _know_ my version is good, I do
>
> 	git diff --ours <file> | git apply -R
>
> This also updates the index.

Good suggestion, but apply does not update the index without
being told to do so with --index, so I think the commandline
should be:

	git diff --ours <path> | git apply -R --index

^ permalink raw reply

* Re: [PATCH] Store peeled refs in packed-refs (take 2).
From: Marco Costalba @ 2006-11-22 12:25 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git
In-Reply-To: <7vac2kvsos.fsf@assigned-by-dhcp.cox.net>

On 11/22/06, Junio C Hamano <junkio@cox.net> wrote:
> there is only one such extension defined, "peeled".  This stores
> the "peeled tag" on a line that immediately follows a line for a
> tag object itself in the format "^<sha-1>".
>

Thanks, I will try to do some performance tests this week end.


^ permalink raw reply

* Re: Adding glob support to remotes
From: Junio C Hamano @ 2006-11-22 12:56 UTC (permalink / raw)
  To: Andy Parkins; +Cc: git
In-Reply-To: <200611220904.21850.andyparkins@gmail.com>

Andy Parkins <andyparkins@gmail.com> writes:

> I started to add code to git-parse-remote.sh:canon_refs_list_for_fetch() to 
> preprocess the reflist to catch lines with a "*" in them then use the remote 
> pattern to filter the output of from "git-ls-remote -h", blah, blah, you get 
> the idea...
>
> However, git-ls-remote needs the name of the remote repository (of course), 
> but that isn't directly available in git-parse-remote.sh.  

Is it really the case?  I do not remember the details offhand,
but I do not think canon_refs_list_for_fetch is the function you
should be messing with to implement the remote."origin".fetch
stuff.  It should be get_remote_default_refs_for_fetch().  The
function returns the list based on which remote, so it surely
knows which remote the caller is talking about.

However, I would recommend against actually running ls-remote to
help "git-fetch" inside git-parse-remote.sh.  I think you should
run ls-remote upfront early in git-fetch because there are at
least two other parts in git-fetch that wants the same ls-remote
output:

 (1) dumb protocols currently cannot deal with a remote that has
     run "packed-ref --prune" because git-fetch.sh first uses
     curl executable to download the loose ref, read it and then
     use the object name read from that to drive git-http-fetch.
     We can and should get rid of the /max_depth=5/,/done/ loop
     there and replace it with a grep of ls-remote output to
     make them work against such a remote.  When tracking many
     branches from the remote, this would reduce the number of
     http requests (one per branch vs a ls-remote which is just
     a single download of info/refs).

 (2) when doing a fetch with tracking branches (which is what
     your change is about), we would need to run ls-remote to
     find out the remote tags for tag following purposes anyway.
     Running "ls-remote -h" once for your purpose and then
     "ls-remote -t" for tag following later is obviously very
     wasteful.


^ permalink raw reply

* Re: Struggling with tangled
From: Johannes Schindelin @ 2006-11-22 13:30 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git
In-Reply-To: <7vu00rsnho.fsf@assigned-by-dhcp.cox.net>

Hi,

On Wed, 22 Nov 2006, Junio C Hamano wrote:

> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
> 
> > 	git diff --ours <file> | git apply -R
> >
> > This also updates the index.
> 
> Good suggestion, but apply does not update the index without
> being told to do so with --index, so I think the commandline
> should be:
> 
> 	git diff --ours <path> | git apply -R --index

Oops. Thanks!
Dscho

^ permalink raw reply

* Re: [Patch to DRAFT 2 (2/2)] Branching and merging with git
From: Rene Scharfe @ 2006-11-22 13:36 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: linux, git
In-Reply-To: <7vlkm3u4kl.fsf@assigned-by-dhcp.cox.net>

Junio C Hamano schrieb:
> -tools will not let you commit to the branch.
> +tools will not let you commit to the remote branchbranch.

s/branchbranch/branch/


^ permalink raw reply

* Re: Stupid Git question
From: Sean Kelley @ 2006-11-22 14:28 UTC (permalink / raw)
  To: git
In-Reply-To: <ejvs65$vo8$1@sea.gmane.org>

On 11/21/06, Jakub Narebski <jnareb@gmail.com> wrote:
> Sean Kelley wrote:
>
> > git checkout origin
>
> It should be "git checkout master". You shouldn't do work on tracking
> branches like origin branch.
>
> > git pull . fm-modulator
> >
> > git push origin
>
> Here origin means origin remote (repository). Check out what you have in
> remotes/origin, or in [remote "origin"] section in git config.


Thanks!  One more question.  It appears that the problem that I am
having is that people are comitting to origin and should be committing
to master.  Perhaps the names can be confusing.  One suggestion made
is that we give a branch on the remote server a more meaningful name.

If on my remote server I have:

  /data/git/kernel/mh.git

How do I add a branch to the remote repository that is visible to all
team members.  It seems like the git checkout -b commands just create
local topic branches.

So I would have something like:

git clone git+ssh://git.example.com/data/git/kernel/mh.git kernel
cd kernel
git checkout Project
git checkout -b fm-modulator
edit/add/commit changes...

git checkout Project
git pull . fm-modulator
git push origin Project

So how do I create this Project branch on the remote repository such
that it is visible to all?  Do I log onto the remove server and do it
manually?  If so, how is that done?

Thanks!

Sean

> --
> Jakub Narebski
> Warsaw, Poland
> ShadeHawk on #git
>
>
> -
> To unsubscribe from this list: send the line "unsubscribe git" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
>


-- 

^ permalink raw reply

* Re: Adding glob support to remotes
From: Andy Parkins @ 2006-11-22 14:41 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano
In-Reply-To: <7v7ixnskql.fsf@assigned-by-dhcp.cox.net>

On Wednesday 2006 November 22 12:56, Junio C Hamano wrote:

> > However, git-ls-remote needs the name of the remote repository (of
> > course), but that isn't directly available in git-parse-remote.sh.
>
> Is it really the case?  I do not remember the details offhand,
> but I do not think canon_refs_list_for_fetch is the function you
> should be messing with to implement the remote."origin".fetch
> stuff.  It should be get_remote_default_refs_for_fetch().  The
> function returns the list based on which remote, so it surely
> knows which remote the caller is talking about.

The problem is that canon_refs_list_for_fetch bombs out too early because "*" 
is not an acceptable name for a ref.

> However, I would recommend against actually running ls-remote to
> help "git-fetch" inside git-parse-remote.sh.  I think you should
> run ls-remote upfront early in git-fetch because there are at
> least two other parts in git-fetch that wants the same ls-remote
> output:

Okay.  That's what I'll do.  It means altering git-check-ref-format to prevent 
the early bomb out.  Perhaps I should move this check to somewhere after I've 
done the reflist expansion?

>  (1) dumb protocols currently cannot deal with a remote that has

I'm not sure I've understood this point.  I shall look at git-fetch.sh more 
closely to try and address this though.

>  (2) when doing a fetch with tracking branches (which is what

Accepted.



Andy

-- 
Dr Andy Parkins, M Eng (hons), MIEE

^ permalink raw reply

* Re: [DRAFT 2] Branching and merging with git
From: linux @ 2006-11-22 15:17 UTC (permalink / raw)
  To: junkio, kent; +Cc: git, linux
In-Reply-To: <m3d57g629q.fsf@ceres.unit.liu.se>

Thank you both for the fixes.  The features that I didn't know about!

On a higher-level note, is it getting too long?

The explanations of inter-repository sharing and branch
manipulation complement each other nicely, but I'm worried the
combination is a bit intimidating in size.

Still, people will want to know most of it eventually, so maybe it's

^ permalink raw reply

* Re: Computing delta sizes in pack files
From: Jonas Fonseca @ 2006-11-22 16:44 UTC (permalink / raw)
  To: Shawn Pearce; +Cc: git
In-Reply-To: <20061121053942.GA3149@spearce.org>

On 11/21/06, Shawn Pearce <spearce@spearce.org> wrote:
> Of course this only looks at a single blob object and does not take
> into account the tree and commit overheads for a given revision,
> but it does give a really good idea of what is going on.

I have some numbers that also includes the other object types. They are
based on running a set of scripts in 5 different repositories. First,
each repository has been both packed and unpacked with respect to the
different object types to show the compression level and disk-space
saving. The main results are compression level for the different object
types and a test to see which pack sizes provide "optimal" packing. I
will not post the numbers here. They are available in
http://jonas.nitro.dk/tmp/stats.pdf for those interested. The following
is my "analysis" of the numbers.

It can be seen that the blob objects generally control the overall
packing properties of the repository, especially when it comes to the
compression level. Generally, there are more tree objects than blob
objects, which can be due to the fact that both the ELinks and Linux
kernel tree are structured into many subdirectories containing few
files.  The Tig repository is exceptional in that it has only few files
and one tree object per revision, which has the effect of reducing the
tree object compression level.  At 83% on average, tree objects compress
very well and in the general case better than blob objects.  As
expected, the randomness of the content of both commit and tag objects
results in a very poor packing performance of only 2%.  In terms of
disk-space usage between packed and unpacked object stores, it is
obvious that the overhead of many small object files is unavoidable.

Next, is the examination of how different pack sizes affect the
compression level. This also includes looking at how the size of the
index file varies.  The columns on per-object sizes are of interest. The
per-object sizes can be compared with the average object size of each
project to get a rough idea of the compression level. In some of the
tables, rows are missing because not all repositories contain enough
objects with respect to the pack sizes being examined.

The data show that for minimal index files, the packs need to contain
more than 2500 objects. The 24 bytes per-object for the optimal case
includes 20-bytes for the object SHA1, and thus cannot be expected to
become lower. For commit objects, the numbers show that there is almost
no difference between small and big packs. The same is assumed to be the
case for tag objects. For tree objects, it becomes very clear that in
repositories with structured source trees, the tree objects compress
much better for big packs, whereas a project, such as Git, does not save
much after pack files reach a size of 250 objects.  Across all
repositories, bigger packs always leads to better compression and fewer
bytes per object, but only for blob objects.

In conclusion, the heuristic of packing based on object type is very
good. Neither commit nor tag objects compress very well when packed.
While both tree and blob objects compress well, tree objects do not
require bigger packs to provide better compression. The pack index files
do not require many objects to become optimal. It should also be noted
from looking at the columns containing numbers about the minimum and
maximum pack sizes that they can vary a lot compared to the average
size.

-- 

^ permalink raw reply

* Re: Stupid Git question
From: Carl Worth @ 2006-11-22 16:44 UTC (permalink / raw)
  To: Sean Kelley; +Cc: git
In-Reply-To: <89b129c60611220628l59e305b8h4d2196f7cf6498d4@mail.gmail.com>

[-- Attachment #1: Type: text/plain, Size: 407 bytes --]

On Wed, 22 Nov 2006 08:28:58 -0600, "Sean Kelley" wrote:
> How do I add a branch to the remote repository that is visible to all
> team members.  It seems like the git checkout -b commands just create
> local topic branches.

Just push the branch out to the remote repository. You even gave the
command sequence to do that:

> git checkout Project
> git pull . fm-modulator
> git push origin Project

-Carl

[-- Attachment #2: Type: application/pgp-signature, Size: 189 bytes --]

^ permalink raw reply

* Re: A documentation to-do list
From: Michael K. Edwards @ 2006-11-22 17:18 UTC (permalink / raw)
  To: Johannes Schindelin; +Cc: Chris Riddoch, git
In-Reply-To: <Pine.LNX.4.63.0611221044180.30004@wbgn013.biozentrum.uni-wuerzburg.de>

On 11/22/06, Johannes Schindelin <Johannes.Schindelin@gmx.de> wrote:
> So, finally my request: we should _organize_ the documentation such that
> your average Joe Programmer is able to get started with git in 1 minute.

I would modify that to a sort of "choose your own adventure" alternative:
    How to use git mindlessly (branchlessly) in 1 minute, by
pretending it's CVS with funny syntax;
vs.
    How git can make you a better programmer in 1 day, by encouraging
you to think about, experiment with, and comment on interactions
between how you and others are evolving a shared code base.

Cheers,

^ permalink raw reply

* StGit metadata grabbing with git clone
From: Otavio Salvador @ 2006-11-22 12:05 UTC (permalink / raw)
  To: git

Hello,

I'm a happy user of stgit together with git to maintain a patch queue
while I or the company team is working on patches that will be send
for merging. Both works great but we're having troubles when we try to
clone a stgit repository.

When I clone the repository it grab the source but it loses the
metadata. I would like to grab those too. Does anybody has a solution
or a trick how I can do that?

-- 
        O T A V I O    S A L V A D O R
---------------------------------------------
 E-mail: otavio@debian.org      UIN: 5906116
 GNU/Linux User: 239058     GPG ID: 49A5F855
 Home Page: http://otavio.ossystems.com.br
---------------------------------------------
"Microsoft gives you Windows ... Linux gives

^ permalink raw reply

* Re: Struggling with tangled
From: Alan Chandler @ 2006-11-22 19:15 UTC (permalink / raw)
  To: git
In-Reply-To: <ek1aj0$u09$1@sea.gmane.org>

On Wednesday 22 November 2006 11:01, Jakub Narebski wrote:
> Alan Chandler wrote:
...
> > 2) Some files get completely munged with conflict resolution markers
> > every few lines.  Is there a simple way to say "don't use this file, but
> > use the [stage2/stage3] sources of the merge". (ie one of the original
> > inputs to the merge - and if so, which one is which)
>
> "git cat-file -p :<stage>: <filename> > <filename>", where stage = 1 means
> version from the ancestor, stage = 2 means version from the HEAD (from the
> base), and stage = 3 means version from the remote/other branch (from the
> branch being rebased).

Just a comment for the list.  There have been lots of documentation ideas 
floating around recently.  One thing that could be done is to cross reference 
the key sections in the man pages somewhat.  I read the man page for 
git-cat-file, and that just says the non flag parameter is <object> and its 
an SHA1.

Once I saw this, I was puzzled and eventually found a reference to in in 
git-rev-parse.


>
> > 3) I sometime hit a merge conflict in a file which I know will actually
> > be deleted at the tip of the topic I am rebasing.  Is there a way at this
> > point to just tell the conflict resolution to say make this file go away.
>
> "git rm <filename>" plus "git update-index <filename>" doesn't work?

Well I _thought_ I tried git-update-index --remove and that hadn't worked.

>
> > 4) I repeat the question I asked in a thread above.  What is the --merge
> > switch on git-rebase actually do.  The man page starts talking about
> > merge strategies, but there already is a -s switch for that.
>
> "git rebase" uses "git format-patch" + "git-am --3way" machinery by
> default. The --merge option makes it use merge machinery instead (similar
> to the way "git checkout -m" uses merge strategy IIRC).

Yes but ...

... what does that mean in usage terms?

Why would I want to use one rather than the other?



-- 
Alan Chandler

^ permalink raw reply

* Re: Struggling with tangled
From: Jakub Narebski @ 2006-11-22 19:40 UTC (permalink / raw)
  To: git
In-Reply-To: <200611221915.59073.alan@chandlerfamily.org.uk>

Alan Chandler wrote:

> On Wednesday 22 November 2006 11:01, Jakub Narebski wrote:
>> Alan Chandler wrote:

>>> 3) I sometime hit a merge conflict in a file which I know will actually
>>> be deleted at the tip of the topic I am rebasing.  Is there a way at this
>>> point to just tell the conflict resolution to say make this file go away.
>>
>> "git rm <filename>" plus "git update-index <filename>" doesn't work?
> 
> Well I _thought_ I tried git-update-index --remove and that hadn't worked.

I think "git update-index --force-remove" might be needed here.

>>> 4) I repeat the question I asked in a thread above.  What is the --merge
>>> switch on git-rebase actually do.  The man page starts talking about
>>> merge strategies, but there already is a -s switch for that.
>>
>> "git rebase" uses "git format-patch" + "git-am --3way" machinery by
>> default. The --merge option makes it use merge machinery instead (similar
>> to the way "git checkout -m" uses merge strategy IIRC).
> 
> Yes but ...
> 
> ... what does that mean in usage terms?
> 
> Why would I want to use one rather than the other?

Merge machinery can detect renames. I don't know if merge machinery supports
skipping over commits. The format-patch/am --3way machinery was first (and
is default).
-- 
Jakub Narebski
Warsaw, Poland
ShadeHawk on #git


^ permalink raw reply

* Re: [PATCH] xdiff: add xdl_merge()
From: Ramsay Jones @ 2006-11-22 19:58 UTC (permalink / raw)
  To: Johannes Schindelin; +Cc: Davide Libenzi, git, Junio C Hamano
In-Reply-To: <Pine.LNX.4.63.0611212316080.26827@wbgn013.biozentrum.uni-wuerzburg.de>

[-- Attachment #1: Type: text/plain, Size: 3583 bytes --]

Hi Johannes,

Johannes Schindelin wrote:
> This new function implements the functionality of RCS merge, but
> in-memory. It returns < 0 on error, otherwise the number of conflicts.
> 

I had a similar idea (removing the RCS dependency) and, about two months
ago, implemented a git-diff3 that used the internal xdiff library.
Unfortunately, I then got distracted by other things and left this as a
WIP, without posting it to the list. [I had an email exchange with Junio
about it, but then dropped the ball - sorry Junio!]

> Finding the conflicting lines can be a very expensive task. You can
> control the eagerness of this algorithm:
> 
> - a level value of 0 means that all overlapping changes are treated
>   as conflicts,
> - a value of 1 means that if the overlapping changes are identical,
>   it is not treated as a conflict.
> - If you set level to 2, overlapping changes will be analyzed, so that
>   almost identical changes will not result in huge conflicts. Rather,
>   only the conflicting lines will be shown inside conflict markers.
> 
> With each increasing level, the algorithm gets slower, but more accurate.
> Note that the code for level 2 depends on the simple definition of
> mmfile_t specific to git, and therefore it will be harder to port that
> to LibXDiff.
> 

Erm, I guess I need to read the code! I haven't had an opportunity to
do that yet, but I hope to have time at the weekend.

> 	My hopes are that when I wake up in the morning, all bugs are
> 	fixed, git-merge-one-file is rewritten as a builtin,
> 	git-merge-index defaults to calling xdl_merge() directly when
> 	no program is passed with "-o", and git-merge-recursive also
> 	avoids fork()ing RCS' merge.

Yep, I had a similar todo list.

> 
> 	A funny side effect is that you can merge with white space
> 	corruption by setting the xpparam flags to ignore whitespace.
> 	The file passed as mf1 wins over mf2 in that case.

Yes, I had this on my todo list. In fact you can already try it out
by using --diff-program="diff -wb" (which uses the external diff).
[To be fair, you can also write an obvious script and use GNU diff3]

> 
>  Makefile       |    3 
>  xdiff/xdiff.h  |    7 +
>  xdiff/xdiffi.c |    3 
>  xdiff/xdiffi.h |    1 
>  xdiff/xmerge.c |  433 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
>  5 files changed, 444 insertions(+), 3 deletions(-)
> 

 From the above, I would guess that your code integrates well with the
xdiff library and, as a result, would be more memory efficient than my
code. I had considered doing this, but I wanted to support an external
diff option, so it was easier to implement the diff3 algorithm outside
the library, treating it just like an external diff (that I didn't need
to fork()/exec()). Also, I didn't need to understand the xdiff code, I
just treat it as a black box (and I'm lazy!).

[Having an external diff option was actually a debugging aid to ensure
that the main diff3 algorithm was implemented correctly. In the end I
didn't use it for debugging anyway!]

[snip patch]

I have attached a version of my patch, against v1.4.2, just for the
interested. Also it contains some tests which I found very helpful
in squashing bugs, so you might like to give them a try with your code.

Note: the fact that the patch is against v1.4.2 should not cause too
much of a problem, since it is mostly new files and the changes to
Makefile, builtin.h and git.c you could probably do in your sleep.

[Junio, this is almost exactly the same patch I sent you last time,
modulo some minor code clean-up/formatting]

All the best,

Ramsay



[-- Attachment #2: p0016.txt --]
[-- Type: text/plain, Size: 52987 bytes --]

>From f23d8df5d7774cb85b834357019c725cf8c1e231 Mon Sep 17 00:00:00 2001
From: Ramsay Jones <ramsay@ramsay1.demon.co.uk>
Date: Mon, 13 Nov 2006 23:04:37 +0000
Subject: [PATCH] Initial implementation of builtin diff3

The intention of this work is to remove the dependancy on
the rcs merge program. As a first step, we implement a
builtin clone of diff3. (A builtin merge replacement command
will be able to reuse this code, since merge is a thin
wrapper around diff3). So git-diff3 is only a stepping stone
to a merge replacement and is not meant to stay!

Signed-off-by: Ramsay Jones <ramsay@ramsay1.demon.co.uk>
---
 Makefile         |    5 
 builtin-diff3.c  | 1824 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 builtin.h        |    2 
 git.c            |    1 
 t/txxxx-diff3.sh |  175 +++++
 t/txxxx/lao      |   11 
 t/txxxx/lao1     |    8 
 t/txxxx/lao2     |   16 
 t/txxxx/lao3     |   13 
 t/txxxx/lao4     |   12 
 t/txxxx/tao      |   14 
 t/txxxx/tao1     |   10 
 t/txxxx/tao2     |   16 
 t/txxxx/tao3     |   13 
 t/txxxx/tao4     |   15 
 t/txxxx/tzu      |   13 
 16 files changed, 2146 insertions(+), 2 deletions(-)

diff --git a/Makefile b/Makefile
index 0761d6c..60f5adc 100644
--- a/Makefile
+++ b/Makefile
@@ -198,7 +198,7 @@ BUILT_INS = git-log$X git-whatchanged$X 
 	git-apply$X git-show-branch$X git-diff-files$X git-update-index$X \
 	git-diff-index$X git-diff-stages$X git-diff-tree$X git-cat-file$X \
 	git-fmt-merge-msg$X git-prune$X git-mv$X git-prune-packed$X \
-	git-repo-config$X
+	git-repo-config$X git-diff3$X
 
 # what 'all' will build and 'install' will install, in gitexecdir
 ALL_PROGRAMS = $(PROGRAMS) $(SIMPLE_PROGRAMS) $(SCRIPTS)
@@ -255,7 +255,8 @@ BUILTIN_OBJS = \
 	builtin-diff-index.o builtin-diff-stages.o builtin-diff-tree.o \
 	builtin-cat-file.o builtin-mailsplit.o builtin-stripspace.o \
 	builtin-update-ref.o builtin-fmt-merge-msg.o builtin-prune.o \
-	builtin-mv.o builtin-prune-packed.o builtin-repo-config.o
+	builtin-mv.o builtin-prune-packed.o builtin-repo-config.o \
+	builtin-diff3.o
 
 GITLIBS = $(LIB_FILE) $(XDIFF_LIB)
 LIBS = $(GITLIBS) -lz
diff --git a/builtin-diff3.c b/builtin-diff3.c
new file mode 100644
index 0000000..9f5698f
--- /dev/null
+++ b/builtin-diff3.c
@@ -0,0 +1,1824 @@
+/*
+ * Builtin "git diff3"
+ *
+ */
+#include "cache.h"
+#include "builtin.h"
+#include "xdiff-interface.h"
+#include <sys/wait.h>
+#include <assert.h>
+
+
+#define MAX_DIFF_ARGS 32
+
+/* diff3 options */
+struct diff3_options {
+	unsigned show_all:1;     /* show all changes, including merged       */
+	unsigned overlap_only:1; /* restrict to overlapping changes          */
+	unsigned unmerged_only:1;/* restrict to unmerged non-overlapping     */
+	unsigned initial_tab:1;  /* output initial tab                       */
+	unsigned markers:1;      /* output conflict markers                  */
+	unsigned edscript:1;     /* output an ed script to perform the merge */
+	unsigned merge:1;        /* actually output the merged file          */
+	const char *diff_prog;   /* external diff program (command line)     */
+};
+
+/* input file info */
+struct finfo {
+	const char *name;   /* filename                       */
+	const char *label;  /* label to use for markers       */
+	int         num;    /* 0 = ours, 1 = base, 2 = theirs */
+	mmfile_t    mf;     /* file mmap() info               */
+};
+
+/* pipe info when running external diff */
+struct pinfo {
+	int   fd;   /* pipe read/write fd    */
+	pid_t pid;  /* child pid to wait for */
+};
+
+/* argv info for external diff */
+struct diff_argv {
+	char *args;         /* argument string memory */
+	const char **argv;  /* argument pointer array */
+	int argc;           /* argument count         */
+};
+
+/* callback info used with internal diff */
+struct ecb_info {
+	char *text;  /* pointer to text of diff output    */
+	long size;   /* size of memory block holding text */
+	long used;   /* length of diff output             */
+};
+
+/* type of diff */
+enum {
+	UNKNOWN_FORMAT,
+	NORMAL_FORMAT,
+	UNIFIED_FORMAT,
+	CONTEXT_FORMAT
+};
+
+/* normal format diff hunk header */
+struct hunk_header {
+	int fs, fe;  /* from-file start and end line numbers */
+	int type;    /* 'a' add, 'c' change, 'd' delete      */
+	int ts, te;  /* to-file start and end line numbers   */
+};
+
+/* unified format diff hunk header */
+struct unified_hunk_header {
+	int fs, fc;  /* from-file start line and count */
+	int ts, tc;  /* to-file start line and count   */
+};
+
+/* lines from one file referenced by a hunk */
+struct hunk_lines {
+	int  start;     /* start line number of hunk         */
+	int  end;       /* end line number of hunk           */
+	char **lines;   /* pointer to array of line pointers */
+	int  *lengths;  /* pointer to array of line lengths  */
+	int  size;      /* size of the above arrays          */
+};
+
+/* 2way diff hunk */
+struct diff2_hunk {
+	int               type;   /* hunk type: 'a', 'c', 'd'   */
+	struct hunk_lines from;   /* from-file hunk lines       */
+	struct hunk_lines to;     /* to-file hunk lines         */
+	struct diff2_hunk *next;  /* next hunk in list          */
+};
+
+/* 2way diff */
+struct diff2 {
+	struct finfo      *ff;      /* from file            */
+	struct finfo      *ft;      /* to file              */
+	mmbuffer_t        content;  /* text content of diff */
+	struct diff2_hunk *hunks;   /* list of diff hunks   */
+};
+
+/* a segment (sub-list) of diff2 hunks */
+struct segment {
+	int               from;   /* from-file number          */
+	int               to;     /* to-file number            */
+	struct diff2_hunk *head;  /* head of hunk list segment */
+	struct diff2_hunk *tail;  /* tail of hunk list segment */
+	int               count;  /* count of hunks            */
+};
+
+/* 3way diff hunk */
+struct diff3_hunk {
+	int               type;  /* 'a' all, '1' 1st, '2' 2nd, '3' 3rd */
+	struct hunk_lines f[3];  /* hunk lines for files 0, 1, 2       */
+	struct diff3_hunk *next; /* next hunk in list                  */
+};
+
+/* 3way diff */
+struct diff3 {
+	struct diff2      *diff01;  /* diff ours -> base   */
+	struct diff2      *diff21;  /* diff theirs -> base */
+	struct diff3_hunk *hunks;   /* list of diff3 hunks */
+};
+
+static void init_finfo(struct finfo *f, const char *name, const char *lab, int num)
+{
+	if (f) {
+		f->name  = name;
+		f->label = lab;
+		f->num   = num;
+
+		f->mf.ptr  = 0;
+		f->mf.size = 0;
+	}
+}
+
+static void alloc_line_arrays(int num, struct hunk_lines *h)
+{
+	assert(h);
+	h->lines   = xcalloc(num, sizeof(char *));
+	h->lengths = xcalloc(num, sizeof(int));
+	h->size    = num;
+}
+
+static struct diff2_hunk *new_diff2_hunk(void)
+{
+	struct diff2_hunk *new = xmalloc(sizeof(*new));
+
+	memset(new, 0, sizeof(*new));
+
+	return new;
+}
+
+static void free_diff2_hunk(struct diff2_hunk *p)
+{
+	if (p->from.lines)
+		free(p->from.lines);
+	if (p->from.lengths)
+		free(p->from.lengths);
+
+	if (p->to.lines)
+		free(p->to.lines);
+	if (p->to.lengths)
+		free(p->to.lengths);
+
+	free(p);
+}
+
+static struct diff2 *new_diff2(struct finfo *ff, struct finfo *ft)
+{
+	struct diff2 *new = xmalloc(sizeof(*new));
+
+	memset(new, 0, sizeof(*new));
+
+	new->ff = ff;
+	new->ft = ft;
+
+	return new;
+}
+
+static void free_diff2(struct diff2 *p)
+{
+	struct diff2_hunk *h, *next;
+
+	for (h = p->hunks; h; h = next) {
+		next = h->next;
+		free_diff2_hunk(h);
+	}
+
+	if (p->content.ptr)
+		free(p->content.ptr);
+
+	free(p);
+}
+
+static struct diff3_hunk *new_diff3_hunk(void)
+{
+	struct diff3_hunk *new = xmalloc(sizeof(*new));
+
+	memset(new, 0, sizeof(*new));
+
+	return new;
+}
+
+static void free_diff3_hunk(struct diff3_hunk *p)
+{
+	int i;
+
+	for (i = 0; i < 3; i++) {
+		if (p->f[i].lines)
+			free(p->f[i].lines);
+		if (p->f[i].lengths)
+			free(p->f[i].lengths);
+	}
+
+	free(p);
+}
+
+static struct diff3 *new_diff3(struct diff2 *diff01, struct diff2 *diff21)
+{
+	struct diff3 *new = xmalloc(sizeof(*new));
+
+	memset(new, 0, sizeof(*new));
+
+	new->diff01 = diff01;
+	new->diff21 = diff21;
+
+	return new;
+}
+
+static void free_diff3(struct diff3 *p)
+{
+	struct diff3_hunk *h, *next;
+
+	for (h = p->hunks; h; h = next) {
+		next = h->next;
+		free_diff3_hunk(h);
+	}
+
+	free_diff2(p->diff01);
+	free_diff2(p->diff21);
+
+	free(p);
+}
+
+static int init_mmfile(mmfile_t *mf, const char *file)
+{
+	char *data;
+	long size;
+	struct stat st;
+	int fd;
+
+	assert(mf && file && *file);
+
+	mf->ptr  = 0;
+	mf->size = 0;
+
+	if (lstat(file, &st) < 0)
+		return -1;
+	size = st.st_size;
+	if (!size) {
+		mf->ptr = "";
+		return 0;
+	}
+
+	fd = open(file, O_RDONLY);
+	if (fd < 0)
+		return -1;
+	data = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
+	close(fd);
+	if (data == MAP_FAILED)
+		return -1;
+
+	mf->ptr  = data;
+	mf->size = size;
+	return 0;
+}
+
+static void free_mmfile(mmfile_t *mf)
+{
+	if (mf) {
+		if (mf->ptr && mf->size != 0)
+			munmap(mf->ptr, mf->size);
+		mf->ptr  = 0;
+		mf->size = 0;
+	}
+}
+
+static void map_file(struct finfo *f)
+{
+	if (!f->mf.ptr && (init_mmfile(&f->mf, f->name) < 0))
+		die("can't read file '%s': %s", f->name, strerror(errno));
+}
+
+static int popenv(int argc, const char *argv[], const char *type, struct pinfo *p)
+{
+	int pfd[2];
+	pid_t pid;
+	int fd;
+
+	if (!argv || !type || !p) {
+		errno = EINVAL;
+		return -1;
+	}
+	p->fd = -1;
+	p->pid = 0;
+
+	if (!*type || (*type != 'r' && *type != 'w') || type[1] != 0) {
+		errno = EINVAL;
+		return -1;
+	}
+
+	if (pipe(pfd) < 0)
+		return -1;
+
+	if ((pid = fork()) < 0) {
+		return -1;
+	} else if (pid == 0) {  /* child */
+		if (*type == 'r') {
+			close(pfd[0]);
+			if(pfd[1] != STDOUT_FILENO) {
+				dup2(pfd[1], STDOUT_FILENO);
+				close(pfd[1]);
+			}
+		} else { /* *type == 'w' */
+			close(pfd[1]);
+			if(pfd[0] != STDIN_FILENO) {
+				dup2(pfd[0], STDIN_FILENO);
+				close(pfd[0]);
+			}
+
+		}
+
+#ifdef DUMMY
+		fd = open("/dev/null", O_RDWR);
+		dup2(fd, STDERR_FILENO);
+		close(fd);
+#endif
+
+		execvp(argv[0], (char *const *)argv);
+		_exit(127);
+	}
+
+	/* parent */
+	if (*type == 'r') {
+		close(pfd[1]);
+		fd = pfd[0];
+	} else {
+		close(pfd[0]);
+		fd = pfd[1];
+	}
+
+	p->fd = fd;
+	p->pid = pid;
+
+	return 0;
+}
+
+static int pclosev(struct pinfo *p)
+{
+	int stat;
+
+	if (!p || p->fd == -1 || p->pid == 0) {
+		errno = EINVAL;
+		return -1;
+	}
+
+	close(p->fd);  /* XXX check for close() failure? */
+	p->fd = -1;
+
+	while (waitpid(p->pid, &stat, 0) < 0)
+		if (errno != EINTR)
+			return -1;
+
+	p->pid = 0;
+
+	return stat;
+}
+
+static int split_cmdline(const char *cmdline, int max, struct diff_argv *prog)
+{
+	assert(prog);
+
+	if (cmdline && *cmdline && (prog->args = xmalloc(strlen(cmdline)+2))) {
+
+		const char *p = cmdline;
+		char *a = prog->args;
+		int argc = 0;
+
+		prog->argv = xmalloc(max * sizeof(char *));
+
+		while( *p ) {
+
+			while (*p == ' ' || *p == '\t')
+				p++;
+			if ( !*p )
+				break;
+
+			if (argc >= max)
+				break;
+
+			prog->argv[argc++] = a;
+			while ((*a = *p) && *a != ' ' && *a != '\t')
+				a++,p++;
+			*a++ = 0;
+
+		}
+
+		*a++ = 0;
+		prog->argc = argc;
+		prog->argv[argc] = 0;
+
+		if (argc == 0) {
+			free(prog->args);
+			free(prog->argv);
+			return -1;
+		}
+
+		return 0;
+	}
+
+	return -1;
+}
+
+static struct diff_argv *new_diff_argv(const char *cmdline)
+{
+	struct diff_argv *a = xmalloc(sizeof(*a));
+
+	memset(a, 0, sizeof(*a));
+
+	if (split_cmdline(cmdline, MAX_DIFF_ARGS, a) < 0) {
+		free(a);
+		return 0;
+	}
+
+	return a;
+}
+
+static void free_diff_argv(struct diff_argv *a)
+{
+	if (a) {
+		if (a->args)
+			free(a->args);
+		if (a->argv)
+			free(a->argv);
+		free(a);
+	}
+}
+
+static int read_from_pipe(int fd, mmbuffer_t *content)
+{
+	ssize_t n, size = 1024, total = 0;
+	char *text = xmalloc(size);
+
+	assert(content != 0);
+
+	while ((n = read(fd, text+total, size-total)) > 0) {
+		if (n == size-total) {
+			size *= 2;
+			text = xrealloc(text, size);
+		}
+		total += n;
+	}
+	if (n < 0)
+		return -1;
+
+	if (total == 0)
+		free(text);
+	else {
+		content->ptr  = text;
+		content->size = total;
+	}
+
+	return 0;
+}
+
+static void run_external_diff(struct diff_argv *prog, const char* file1, const char* file2, mmbuffer_t *content)
+{
+	char const **ap;
+	struct pinfo pi;
+	int stat, code;
+
+	if (!prog || !prog->args || !prog->argv || prog->argc < 1)
+		die("internal error: invalid diff argv");
+
+	if (prog->argc > MAX_DIFF_ARGS - 4)
+		die("too many arguments for external diff-program");
+
+	ap = &prog->argv[prog->argc];
+	*ap++ = "--";
+	*ap++ = file1;
+	*ap++ = file2;
+	*ap++ = 0;
+
+	if(popenv(prog->argc+4, prog->argv, "r", &pi) < 0)
+		die("popenv() failed: %s", strerror(errno));
+
+	if (read_from_pipe(pi.fd, content) < 0)
+		die("read_from_pipe() failed: %s", strerror(errno));
+
+	if ((stat = pclosev(&pi)) < 0)
+		die("pclosev() failed: %s", strerror(errno));
+
+	if (!WIFEXITED(stat))
+		die("diff-program '%s' terminated abnormally", prog->argv[0]);
+
+	if ((code = WEXITSTATUS(stat)) == 127)
+		die("can't execute external diff-program '%s'", prog->argv[0]);
+
+	if (code < 0 || code > 1)
+		die("external diff-program '%s' failed", prog->argv[0]);
+}
+
+#ifdef DUMMY
+static void print_buffer(mmbuffer_t *b)
+{
+	ssize_t count = 0, n;
+
+	if (b->size == 0)
+		return;
+
+	while ((n = xwrite(STDOUT_FILENO, b->ptr+count, b->size-count)) > 0)
+		count += n;
+}
+#endif
+
+static void external_diff(struct diff_argv *prog, struct finfo *ff, struct finfo *ft, mmbuffer_t *diff)
+{
+	assert(ff != 0 && ft != 0 && diff != 0);
+
+	run_external_diff(prog, ff->name, ft->name, diff);
+
+#ifdef DUMMY
+	printf("diff %s %s\n", ff->name, ft->name);
+	print_buffer(diff);
+	printf("--------\n");
+#endif
+}
+
+static int xdiff_output(void *priv_, mmbuffer_t *mb, int nbuf)
+{
+	struct ecb_info *b = priv_;
+	int i;
+
+	for (i = 0; i < nbuf; i++) {
+
+		if (b->used + mb[i].size > b->size) {
+			if (b->size > mb[i].size)
+				b->size *= 2;
+			else
+				b->size += mb[i].size;
+			b->text = xrealloc(b->text, b->size);
+		}
+
+		memcpy(b->text+b->used, mb[i].ptr, mb[i].size);
+		b->used += mb[i].size;
+
+	}
+	return 0;
+}
+
+static void run_internal_diff(mmfile_t *mf1, mmfile_t *mf2, mmbuffer_t *diff)
+{
+	xpparam_t xpp;
+	xdemitconf_t xecfg;
+	xdemitcb_t ecb;
+	struct ecb_info ecb_data;
+
+	ecb_data.text = xmalloc(1024);
+	ecb_data.size = 1024;
+	ecb_data.used = 0;
+
+	xpp.flags = XDF_NEED_MINIMAL;
+
+	xecfg.ctxlen = 0;
+	xecfg.flags  = 0;
+
+	ecb.outf = xdiff_output;
+	ecb.priv = &ecb_data;
+
+	xdl_diff(mf1, mf2, &xpp, &xecfg, &ecb);
+
+	if (ecb_data.used == 0)
+		free(ecb_data.text);
+	else {
+		diff->ptr  = ecb_data.text;
+		diff->size = ecb_data.used;
+	}
+}
+
+static void internal_diff(struct finfo *ff, struct finfo *ft, mmbuffer_t *diff)
+{
+	assert(ff != 0 && ft != 0 && diff != 0);
+
+	map_file(ff);
+	map_file(ft);
+
+	run_internal_diff(&ff->mf, &ft->mf, diff);
+
+#ifdef DUMMY
+	printf("diff %s %s\n", ff->name, ft->name);
+	print_buffer(diff);
+	printf("--------\n");
+#endif
+}
+
+
+static void read_diff(struct diff_argv *prog, struct finfo *ff, struct finfo *ft, mmbuffer_t *diff)
+{
+	if (prog)
+		external_diff(prog, ff, ft, diff);
+	else
+		internal_diff(ff, ft, diff);
+}
+
+static char *nextline(char *p, char *limit)
+{
+	while (p < limit)
+		if (*p++ == '\n')
+			return p;
+	return 0;
+}
+
+static int get_num(char **tp, int *np)
+{
+	char *p  = *tp;
+	int  neg = 0;
+	int  num = 0;
+
+	/* can't use: num = strtol(p, tp, 10); because strtol() will
+	 * skip using isspace() which includes '\n'.
+	 */
+
+	while (*p == ' ' || *p == '\t') p++;
+
+	if (*p == '-' || *p == '+') {
+		neg = *p == '-';
+		p++;
+	}
+
+	while (isdigit(*p))
+		num = num * 10 + (*p++ - '0');
+
+	if (*tp == p)
+		return 0;
+
+	*tp = p;
+	*np = (neg) ? -num : num;
+	return 1;
+}
+
+static int have(char **tp, char c)
+{
+	char *p = *tp;
+
+	while (*p == ' ' || *p == '\t') p++;
+
+	if (*p == c) {
+		*tp = ++p;
+		return 1;
+	}
+
+	return 0;
+}
+
+static int at_eol(char **tp)
+{
+	char *p = *tp;
+
+	while (*p == ' ' || *p == '\t') p++;
+	*tp = p;
+
+	if (*p == '\n')
+		return 1;
+
+	return 0;
+}
+
+static int normal_header(char **curp, struct hunk_header *header)
+{
+	char *p = *curp;
+
+	if (!get_num(&p, &header->fs))
+		return 0;
+
+	header->fe = header->fs;
+	if (have(&p, ',')) {
+		if(!get_num(&p, &header->fe))
+			return 0;
+	}
+
+	if (have(&p, 'a'))
+		header->type = 'a';
+	else if (have(&p, 'c'))
+		header->type = 'c';
+	else if (have(&p, 'd'))
+		header->type = 'd';
+	else
+		return 0;
+
+	if (header->type == 'a') {
+		assert(header->fs == header->fe);
+		header->fe--;  /* null (zero) range of lines */
+	}
+
+	if (!get_num(&p, &header->ts))
+		return 0;
+
+	header->te = header->ts;
+	if (have(&p, ',')) {
+		if(!get_num(&p, &header->te))
+			return 0;
+	}
+
+	if (header->type == 'd') {
+		assert(header->ts == header->te);
+		header->te--;  /* null (zero) range of lines */
+	}
+
+	if(!at_eol(&p))
+		return 0;
+
+	*curp = ++p;
+	return 1;
+}
+
+static int determine_diff_format(mmbuffer_t *content, char** curp)
+{
+	char *p     = content->ptr;
+	char *limit = p + content->size;
+	char *p2, *p3, *p4;
+	struct hunk_header head;
+
+	*curp = p;
+
+	if((p2 = nextline(p, limit)) == 0)
+		return UNKNOWN_FORMAT;
+
+	/* internal (unified) diff, no header */
+	if (memcmp(p, "@@ ", 3) == 0)
+		return UNIFIED_FORMAT;
+
+	/* normal format diff */
+	if (normal_header(&p, &head))
+		return NORMAL_FORMAT;
+
+	if((p3 = nextline(p2, limit)) == 0)
+		return UNKNOWN_FORMAT;
+
+	/* external (unified) diff, with header */
+	if (memcmp(p,  "--- ", 4) == 0 &&
+	    memcmp(p2, "+++ ", 4) == 0 &&
+	    memcmp(p3, "@@ ",  3) == 0) {
+
+		*curp = p3;
+		return UNIFIED_FORMAT;
+	}
+
+	/* context diff */
+	if (memcmp(p,  "*** ", 4) == 0 &&
+	    memcmp(p2, "--- ", 4) == 0 &&
+	    memcmp(p3, "****", 4) == 0) {
+
+		if((p4 = nextline(p3, limit)) == 0)
+			return UNKNOWN_FORMAT;
+
+		*curp = p4;
+		return CONTEXT_FORMAT;
+	}
+
+	return UNKNOWN_FORMAT;
+}
+
+static void get_line(char **curp, char *limit, char type, int num, struct hunk_lines *hl)
+{
+	char *p = *curp;
+	int  normal = (type == '<') || (type == '>');
+	char *pn;
+
+	assert(p < limit);
+
+	if (*p++ != type || (normal && *p++ != ' '))
+		die("invalid diff: leading line chars");
+
+	hl->lines[num] = p;
+
+	if ((pn = nextline(p, limit)) == 0)
+		die("invalid diff: missing eol #3");
+
+	hl->lengths[num] = (int)(pn - p);
+
+	/* check for "\ No newline at end of file" */
+	if (pn < limit && *pn == '\\') {
+		/* remove nl from length and skip */
+		hl->lengths[num] = (int)(pn - p) - 1;
+		pn = nextline(pn, limit);
+	}
+
+	*curp = pn;
+}
+
+static void process_normal_diff(struct diff2 *d, char *p)
+{
+	mmbuffer_t *content = &d->content;
+	char *limit = content->ptr + content->size;
+	struct diff2_hunk *hunk, **last_hunk;
+	int i;
+
+	last_hunk = &d->hunks;
+
+	while (p < limit) {
+		struct hunk_header head;
+
+		hunk = new_diff2_hunk();
+
+		/* hunk header: <int>[,<int>]<type><int>[,<int>] */
+		if (!normal_header(&p, &head))
+			die("invalid diff: header");
+
+		hunk->type = head.type;
+
+		/* from-file hunk lines (if any) */
+		hunk->from.start = head.fs;
+		hunk->from.end   = head.fe;
+		if (head.type != 'a') {
+			int numlines = head.fe - head.fs + 1;
+
+			assert(numlines > 0);
+
+			alloc_line_arrays(numlines, &hunk->from);
+
+			for (i = 0; i < numlines; i++)
+				get_line(&p, limit, '<', i, &hunk->from);
+		}
+
+		/* skip change hunk separator (if any) */
+		if (head.type == 'c') {
+			if (memcmp(p, "---\n", 4) != 0)
+				die("invalid diff: missing change separator");
+			p += 4;
+		}
+
+		/* to-file hunk lines (if any) */
+		hunk->to.start = head.ts;
+		hunk->to.end   = head.te;
+		if (head.type != 'd') {
+			int numlines = head.te - head.ts + 1;
+
+			assert(numlines > 0);
+
+			alloc_line_arrays(numlines, &hunk->to);
+
+			for (i = 0; i < numlines; i++)
+				get_line(&p, limit, '>', i, &hunk->to);
+		}
+
+		/* add this hunk to the end of the list */
+		*last_hunk = hunk;
+		last_hunk  = &hunk->next;
+
+	}
+
+	*last_hunk = 0;
+}
+
+static int have_str(char **tp, char *s)
+{
+	char *p = *tp;
+	int  len = strlen(s);
+
+	if (memcmp(p, s, len) == 0) {
+		p += len;
+		*tp = p;
+		return 1;
+	}
+
+	return 0;
+}
+
+static int unified_header(char **curp, struct unified_hunk_header *header)
+{
+	char *p = *curp;
+
+	if (!have_str(&p, "@@ -"))
+		return 0;
+
+	if (!get_num(&p, &header->fs))
+		return 0;
+
+	header->fc = 1;
+	if (have(&p, ',')) {
+		if(!get_num(&p, &header->fc))
+			return 0;
+	}
+
+	if (!have_str(&p, " +"))
+		return 0;
+
+	if (!get_num(&p, &header->ts))
+		return 0;
+
+	header->tc = 1;
+	if (have(&p, ',')) {
+		if(!get_num(&p, &header->tc))
+			return 0;
+	}
+
+	if (!have_str(&p, " @@"))
+		return 0;
+
+	if(!at_eol(&p))
+		return 0;
+
+	*curp = ++p;
+	return 1;
+}
+
+static int count_hunk_lines(char *p, char *limit, char type)
+{
+	int num = 0;
+
+	while (p < limit && *p == type) {
+		char *pn;
+
+		if ((pn = nextline(p, limit)) == 0)
+			die("invalid diff: missing eol #1");
+
+		num++;
+		p = pn;
+	}
+
+	return num;
+}
+
+static void process_unified_diff(struct diff2 *d, char *p)
+{
+	mmbuffer_t *content = &d->content;
+	char *limit = content->ptr + content->size;
+	struct diff2_hunk *hunk, **last_hunk;
+	int i;
+
+	last_hunk = &d->hunks;
+
+	while (p < limit) {
+		struct unified_hunk_header head;
+		int single_hunk = 0;
+
+		/* hunk header: @@ -<int>[,<int>] +<int>[,<int>] @@ */
+		if (!unified_header(&p, &head))
+			die("invalid diff: header");
+
+		single_hunk = (head.fc == 0 || head.tc == 0);
+
+		for (;;) {  /* for all hunks */
+			int cc = 0, fc = 0, tc = 0;
+
+			/* find begining of next hunk: skip common lines */
+			while (p < limit && *p == ' ') {
+				char *pn;
+
+				cc++;
+				head.fs++; head.fc--;
+				head.ts++; head.tc--;
+
+				if ((pn = nextline(p, limit)) == 0)
+					die("invalid diff: missing eol #2");
+				p = pn;
+			}
+
+			/* finished? */
+			if (limit <= p || *p == '@') {
+				assert(head.fc == 0 && head.tc == 0);
+				break;
+			}
+			if (*p != '-' && *p != '+')
+				die("invalid diff: missing -/+ lines");
+
+			hunk = new_diff2_hunk();
+
+			hunk->from.start = hunk->from.end = head.fs;
+			hunk->to.start   = hunk->to.end   = head.ts;
+
+			/* from-file hunk lines (if any) */
+			fc = count_hunk_lines(p, limit, '-');
+			if (fc > 0) {
+				alloc_line_arrays(fc, &hunk->from);
+
+				for (i = 0; i < fc; i++)
+					get_line(&p, limit, '-', i, &hunk->from);
+
+				hunk->from.end += fc-1;
+
+				head.fs += fc;
+				head.fc -= fc;
+			}
+
+			/* to-file hunk lines (if any) */
+			tc = count_hunk_lines(p, limit, '+');
+			if (tc > 0) {
+				alloc_line_arrays(tc, &hunk->to);
+
+				for (i = 0; i < tc; i++)
+					get_line(&p, limit, '+', i, &hunk->to);
+
+				hunk->to.end += tc-1;
+
+				head.ts += tc;
+				head.tc -= tc;
+			}
+
+			/* set hunk type */
+			assert(fc > 0 || tc > 0);
+			if (fc == 0) {
+				hunk->type = 'a';
+				if (!single_hunk)
+					hunk->from.start--;
+				hunk->from.end = hunk->from.start - 1;
+			} else if (tc == 0) {
+				hunk->type = 'd';
+				if (!single_hunk)
+					hunk->to.start--;
+				hunk->to.end = hunk->to.start - 1;
+			} else {
+				hunk->type = 'c';
+			}
+
+			/* add this hunk to the end of the list */
+			*last_hunk = hunk;
+			last_hunk  = &hunk->next;
+		}
+
+	}
+
+	*last_hunk = 0;
+
+}
+
+struct diff2* process_diff(struct diff_argv *prog, struct finfo *ff, struct finfo *ft)
+{
+	struct diff2 *result = new_diff2(ff, ft);
+	char *scan;
+
+	read_diff(prog, ff, ft, &result->content);
+
+	if (result->content.size == 0)
+		return result;  /* empty diff is ok */
+
+	assert(result->content.size > 0);
+
+	if (result->content.ptr[result->content.size-1] != '\n')
+		die("invalid diff: missing eol on last line");
+
+	switch (determine_diff_format(&result->content, &scan)) {
+		case NORMAL_FORMAT:
+			process_normal_diff(result, scan);
+			break;
+		case UNIFIED_FORMAT:
+			process_unified_diff(result, scan);
+			break;
+		case CONTEXT_FORMAT:
+			die("context diff format not supported");
+			break;
+		case UNKNOWN_FORMAT:
+		default:
+			die("unknown diff format");
+			break;
+	}
+
+	return result;
+}
+
+static void adjust_diff2(struct diff2 *d)
+{
+	struct diff2_hunk *p;
+
+	for (p = d->hunks; p; p = p->next) {
+		if (p->type == 'a') {
+			p->from.start++;
+			p->from.end++;
+		} else if (p->type == 'd') {
+			p->to.start++;
+			p->to.end++;
+		}
+	}
+}
+
+static int copy_lines(struct hunk_lines *dst, int off, struct hunk_lines *src)
+{
+	char **s = src->lines;
+	int  *sl = src->lengths;
+	char **d = dst->lines + off;
+	int  *dl = dst->lengths + off;
+	int  n   = src->size;
+
+	assert(n <= dst->size - off);
+
+	while(n--) {
+		if (*d) {
+			if (*sl != *dl || memcmp(*s, *d, *sl)) {
+				return 0;
+			}
+		} else {
+			*d  = *s;
+			*dl = *sl;
+		}
+		d++; dl++;
+		s++; sl++;
+	}
+	return 1;
+}
+
+static int equal_lines(struct hunk_lines *lhs, struct hunk_lines *rhs)
+{
+	char **l = lhs->lines;
+	int  *ll = lhs->lengths;
+	char **r = rhs->lines;
+	int  *rl = rhs->lengths;
+	int  n   = lhs->size;
+
+	if (n != rhs->size)
+		return 0;
+
+	while (n--) {
+		if (!*l || !*r || *ll != *rl || memcmp(*l, *r, *ll))
+			return 0;
+		l++; ll++;
+		r++; rl++;
+	}
+	return 1;
+}
+
+static int get_low_thread(struct diff2_hunk *p[2])
+{
+	if (!p[0])
+		return 1;
+	if (!p[1])
+		return 0;
+	return (p[0]->to.start > p[1]->to.start) ? 1 : 0;
+}
+
+static void init_segment(struct segment *s, int from, int to)
+{
+	s->from  = from;
+	s->to    = to;
+	s->head  = 0;
+	s->tail  = 0;
+	s->count = 0;
+}
+
+static void add_segment(struct segment *s, struct diff2_hunk *d)
+{
+	assert(d != 0);
+
+	if (!s->head)
+		s->head = s->tail = d;
+	else
+		s->tail = d;
+	s->count++;
+}
+
+static struct diff3_hunk *make_diff3_hunk(struct segment use[2],
+		int low_thread, int high_thread, struct diff3_hunk *last)
+{
+	struct diff3_hunk *result = new_diff3_hunk();
+	int startc, endc;
+	int i, common;
+	int from0, from1;
+
+	assert(use[0].to == use[1].to);
+	assert(use[0].from != use[1].from);
+
+	from0 = use[0].from;
+	from1 = use[1].from;
+
+	common = use[0].to;
+	assert(common >= 0 && common <= 2);
+
+	/* find start/end line numbers from the common file */
+	startc = result->f[common].start = use[low_thread].head->to.start;
+	endc   = result->f[common].end   = use[high_thread].tail->to.end;
+
+	/* find start/end line numbers for other files */
+	for (i = 0; i < 2; i++) {
+		struct segment *s = &use[i];
+		struct diff2_hunk *h = s->head;
+		struct diff2_hunk *t = s->tail;
+		int file = s->from;
+		int start = startc, end = endc;
+
+		assert(file >= 0 && file <= 2);
+
+		if (h) {
+			start = startc - h->to.start + h->from.start;
+			end   = endc   - t->to.end   + t->from.end;
+		} else if (last) {
+			int d = last->f[file].end - last->f[common].end;
+			start = startc + d;
+			end   = endc   + d;
+		}
+		result->f[file].start = start;
+		result->f[file].end   = end;
+	}
+
+	/* allocate hunk_lines line pointer and lengths arrays */
+	for (i = 0; i < 3; i++) {
+		int numlines = result->f[i].end - result->f[i].start + 1;
+
+		if (numlines > 0)
+			alloc_line_arrays(numlines, &result->f[i]);
+	}
+
+	/* copy line info for common file */
+	for (i = 0; i < 2; i++) {
+		struct segment *s = &use[i];
+		struct diff2_hunk *h = s->head;
+		int j;
+
+		for (j = 0; j < s->count; j++,h = h->next) {
+			int offset = h->to.start - startc;
+
+			if (!copy_lines(&result->f[common], offset, &h->to))
+				die("internal error: copy_lines()");
+
+		}
+	}
+
+	/* copy line info for other files */
+	for (i = 0; i < 2; i++) {
+		struct segment *s = &use[i];
+		struct diff2_hunk *h = s->head;
+		struct hunk_lines *tf, *cf;
+		int start, end, file = s->from;
+		int j, k, l, m;
+
+		assert(file >= 0 && file <= 2);
+
+		tf = &result->f[file];
+		cf = &result->f[common];
+
+		start = tf->start;
+		end   = tf->end;
+
+		/* before first hunk */
+		k = (h) ? h->from.start : end+1;
+		for (j = 0; j + start < k; j++) {
+			tf->lines[j]   = cf->lines[j];
+			tf->lengths[j] = cf->lengths[j];
+		}
+
+		/* hunk list */
+		for (j = 0; j < s->count; j++,h = h->next) {
+			int offset = h->from.start - start;
+
+			if (!copy_lines(&result->f[file], offset, &h->from))
+				die("internal error:");
+
+			/* between this and next hunk */
+			l = h->to.end + 1 - startc;
+			if (j < s->count-1)
+				k = h->next->from.start - start;
+			else
+				k = end+1 - start;
+			for (m = h->from.end + 1 - start; m < k; m++,l++) {
+				tf->lines[m]   = cf->lines[l];
+				tf->lengths[m] = cf->lengths[l];
+			}
+
+		}
+
+	}
+
+	/* set diff3 hunk type */
+	if (!use[0].head)
+		result->type = '0' + from1;
+	else if (!use[1].head)
+		result->type = '0' + from0;
+	else {
+		if (equal_lines(&result->f[from0], &result->f[from1]))
+			result->type = '0' + common;
+		else
+			result->type = 'a';
+	}
+
+	return result;
+}
+
+static struct diff3 *make_3way_diff(struct diff2 *d01, struct diff2 *d21)
+{
+	struct diff3 *result = new_diff3(d01, d21);
+	struct diff2_hunk *diff01 = d01->hunks;
+	struct diff2_hunk *diff21 = d21->hunks;
+	struct diff2_hunk *p[2], *high_diff, *other_diff;
+	struct diff3_hunk *hunk, **last_hunk;
+	struct diff3_hunk *last;
+	struct segment use[2];
+	int low_thread, high_thread;
+	int other_thread, high_line;
+
+	assert(d01->ft->num == d21->ft->num);
+	assert(d01->ff->num != d21->ff->num);
+
+	adjust_diff2(d01);
+	adjust_diff2(d21);
+
+	last_hunk = &result->hunks;
+	last = 0;
+	p[0] = diff01;
+	p[1] = diff21;
+
+	while (p[0] || p[1]) {
+		init_segment(&use[0], d01->ff->num, d01->ft->num);
+		init_segment(&use[1], d21->ff->num, d21->ft->num);
+
+		high_thread = low_thread = get_low_thread(p);
+
+		high_diff = p[high_thread];
+		high_line = high_diff->to.end;
+
+		add_segment(&use[high_thread], high_diff);
+		p[high_thread] = high_diff->next;
+
+		other_thread = high_thread ^ 0x1;
+		other_diff   = p[other_thread];
+
+		while (other_diff && other_diff->to.start <= high_line+1) {
+
+			add_segment(&use[other_thread], other_diff);
+			p[other_thread] = p[other_thread]->next;
+
+			if (high_line < other_diff->to.end) {
+				high_thread ^= 0x1;
+				high_diff = other_diff;
+				high_line = other_diff->to.end;
+			}
+
+			other_thread = high_thread ^ 0x1;
+			other_diff   = p[other_thread];
+		}
+
+		hunk = make_diff3_hunk(use, low_thread, high_thread, last);
+
+		if (!hunk)
+			die("can't create diff3 hunk");
+
+		/* add this hunk to the end of the list */
+		*last_hunk = hunk;
+		last_hunk  = &hunk->next;
+
+		last = hunk;
+	}
+
+	*last_hunk = 0;
+
+	return result;
+}
+
+#ifdef DUMMY
+static void print_diff2_hunk(struct diff2_hunk *p)
+{
+	struct hunk_lines *f = &p->from;
+	struct hunk_lines *t = &p->to;
+	int i, type = p->type;
+
+	printf("%d,%d %c %d,%d\n", f->start, f->end, type, t->start, t->end);
+
+	for (i = 0; i < f->size; i++)
+		printf("< %.*s", f->lengths[i], f->lines[i]);
+
+	printf("---\n");
+
+	for (i = 0; i < t->size; i++)
+		printf("> %.*s", t->lengths[i], t->lines[i]);
+
+	printf("+++\n");
+}
+
+static void print_diff2(struct diff2 *d)
+{
+	struct diff2_hunk *p;
+
+	printf("<--- diff2 --->\n");
+	for (p = d->hunks; p; p = p->next)
+		print_diff2_hunk(p);
+	printf(">=============<\n");
+}
+#endif
+
+static void print_diff3_hunk(FILE *outf, struct diff3_hunk *p, char *prefix)
+{
+	int i, j, k, skip_print;
+
+	if (p->type == 'a')
+		fprintf(outf, "====\n");
+	else
+		fprintf(outf, "====%c\n", p->type+1);
+
+	switch (p->type) {
+		case '0': skip_print = 1; break;
+		case '1': skip_print = 0; break;
+		case '2': skip_print = 0; break;
+		case 'a': skip_print = 3; break;
+		default:
+			die("internal error: invalid diff3 hunk type");
+			break;
+	}
+
+	for (j = 0,k = 0; j < 3; j++,k++) {
+		int  numlines, start, end, len;
+		char *s;
+
+		if (p->type == '1' && j > 0)
+			k = (j == 1) ? 2 : 1;
+
+		start = p->f[k].start;
+		end   = p->f[k].end;
+
+		numlines = end - start + 1;
+
+		if (numlines == 0)
+			fprintf(outf, "%d:%da\n", k+1, start-1);
+		else if (numlines == 1)
+			fprintf(outf, "%d:%dc\n", k+1, start);
+		else
+			fprintf(outf, "%d:%d,%dc\n", k+1, start, end);
+
+		if (k == skip_print)
+			continue;
+
+		assert(numlines == p->f[k].size);
+
+		if (p->f[k].size <= 0)
+			continue;
+
+		s   = 0;
+		len = 0;
+
+		for (i = 0; i < p->f[k].size; i++) {
+			s = p->f[k].lines[i];
+			len = p->f[k].lengths[i];
+			fprintf(outf, "%s", prefix);
+			fwrite(s, 1, len, outf);
+		}
+
+		if (s && (len == 0 || s[len-1] != '\n'))
+			fprintf(outf, "\n\\ No newline at end of file\n");
+
+	}
+
+}
+
+static void print_diff3(FILE *outf, struct diff3 *d, struct diff3_options *o)
+{
+	struct diff3_hunk *p;
+	char *prefix = (o->initial_tab) ? "\t" : "  ";
+
+	for (p = d->hunks; p; p = p->next)
+		print_diff3_hunk(outf, p, prefix);
+}
+
+static int write_lines(FILE *outf, char **curp, char *limit, int num)
+{
+	char *p = *curp;
+	char *pn;
+	int  size;
+
+	assert(num >= 0);
+
+	while (num--) {
+		if ((pn = nextline(p, limit)) == 0) {
+			if (num > 0)
+				return -1;
+			pn = limit;
+		}
+		p = pn;
+	}
+	size = (int)(p - *curp);
+	if (size > 0)
+		fwrite(*curp, 1, size, outf);
+	*curp = p;
+	return 0;
+}
+
+static int skip_lines(char **curp, char *limit, int num)
+{
+	char *p = *curp;
+	char *pn;
+
+	assert(num >= 0);
+
+	while (num--) {
+		if ((pn = nextline(p, limit)) == 0) {
+			if (num > 0)
+				return -1;
+			pn = limit;
+		}
+		p = pn;
+	}
+	*curp = p;
+	return 0;
+}
+
+static int diff3_merge(FILE *outf, mmfile_t *inf, struct diff3 *d, struct finfo f[3], struct diff3_options *opts)
+{
+	struct diff3_hunk *p;
+	int conflicts = 0;
+	int inf_lines = 0;
+	char *infp, *inf_limit;
+
+	infp = inf->ptr;
+	inf_limit = infp + inf->size;
+
+	for (p = d->hunks; p; p = p->next) {
+		int conflict = 0;
+		int i, lines;
+
+		/* check if we can skip this block */
+		switch (p->type) {
+			default:
+			case '0': /* change unique to our file: skip */
+				continue;
+				break;
+			case '1': /* same in ours and theirs: merged change */
+				if (!opts->show_all)
+					continue;
+				conflict = 1;
+				break;
+			case '2': /* change only in theirs: un-merged change */
+				if (opts->overlap_only)
+					continue;
+				break;
+			case 'a': /* all files differ: overlapping change */
+				if (opts->unmerged_only)
+					continue;
+				conflict = opts->markers;
+				break;
+		}
+
+		/* write lines from '0' (our) file (if any) */
+		lines = p->f[0].start - inf_lines - 1;
+		if (lines > 0) {
+			if (write_lines(outf, &infp, inf_limit, lines) < 0)
+				die("unexpected eof on file");
+			inf_lines += lines;
+		}
+
+		if (conflict) {
+			/* write lines from files with conflict markers */
+			char *marker_fmt = "<<<<<<< %s\n";
+
+			conflicts++;
+
+			if (p->type == 'a') {
+				/* write lines from '0' (our) file */
+				fprintf(outf, marker_fmt, f[0].label);
+				for (i = 0; i < p->f[0].size; i++) {
+					char *s = p->f[0].lines[i];
+					int len = p->f[0].lengths[i];
+					fwrite(s, 1, len, outf);
+				}
+				marker_fmt = "||||||| %s\n";
+			}
+
+			if (opts->show_all) {
+				/* write lines from '1' (base) file */
+				fprintf(outf, marker_fmt, f[1].label);
+				for (i = 0; i < p->f[1].size; i++) {
+					char *s = p->f[1].lines[i];
+					int len = p->f[1].lengths[i];
+					fwrite(s, 1, len, outf);
+				}
+			}
+
+			fprintf(outf, "=======\n");
+
+		}
+
+		/* write lines from '2' (their) file */
+		for (i = 0; i < p->f[2].size; i++) {
+			char *s = p->f[2].lines[i];
+			int len = p->f[2].lengths[i];
+			fwrite(s, 1, len, outf);
+		}
+
+		/* add conflict marker */
+		if (conflict)
+			fprintf(outf, ">>>>>>> %s\n", f[2].label);
+
+		/* skip lines from '0' (our) file */
+		lines = p->f[0].size;
+		if (lines > 0) {
+			if (skip_lines(&infp, inf_limit, lines) < 0)
+				die("unexpected eof");
+			inf_lines += lines;
+		}
+
+	}
+
+	/* write remainder of '0' (our) file */
+	if (infp < inf_limit)
+		fwrite(infp, 1, (int)(inf_limit - infp), outf);
+
+	return conflicts;
+}
+
+int diff3(FILE *outf, const char *file[3], const char *label[3], struct diff3_options *opts)
+{
+	int i;
+	struct finfo f[3];
+	struct diff2 *d1, *d2;
+	struct diff3 *d3;
+	struct diff_argv *prog = 0;
+	int conflicts = 0;
+
+	for ( i = 0; i < 3; i++ )
+		init_finfo(&f[i], file[i], label[i], i);
+
+	if (opts->diff_prog)
+		prog = new_diff_argv(opts->diff_prog);
+
+	d2 = process_diff(prog, &f[2], &f[1]);
+	free_mmfile(&f[2].mf);
+
+	d1 = process_diff(prog, &f[0], &f[1]);
+	free_mmfile(&f[1].mf);
+
+	d3 = make_3way_diff(d1, d2);
+
+	if (opts->merge) {
+		map_file(&f[0]);
+		conflicts = diff3_merge(outf, &f[0].mf, d3, f, opts);
+	} else {
+		print_diff3(outf, d3, opts);
+	}
+
+	free_mmfile(&f[0].mf);
+	free_diff3(d3);  /* also free()'s d1 and d2 */
+	free_diff_argv(prog);
+
+	return conflicts;
+}
+
+
+static const char builtin_diff3_usage[] =
+"git-diff3 [options]... ours base theirs\n"
+"\n"
+"  -e  --ed            output unmerged changes from BASE to THEIRS into OURS\n"
+"  -E  --show-overlap  output unmerged changes, bracketing conflicts\n"
+"  -A  --show-all      output all changes, bracketing conflicts\n"
+"  -x  --overlap-only  output overlapping changes\n"
+"  -X                  output overlapping changes, bracketing them\n"
+"  -3  --easy-only     output unmerged non-overlapping changes\n"
+"\n"
+"  -m  --merge         output merged file instead of ed script\n"
+"  -T  --initial-tab   make tabs line up by prepending a tab\n"
+"\n"
+"  -L LABEL  --label=LABEL  use LABEL instead of filename\n"
+"  --diff-program=PROGRAM   use PROGRAM to compare files\n"
+;
+
+
+int cmd_diff3(int argc, const char **argv, const char *prefix)
+{
+	int i;
+	const char *label_strings[3];
+	int label_count = 0;
+	int conflicts = 0;
+	int edarg = 0;
+	const char **file;
+	struct diff3_options opts;
+
+	memset(&opts, 0, sizeof(opts));
+
+	for (i = 1 ; i < argc ; i++) {
+		const char *arg = argv[i];
+
+		if (*arg != '-')
+			break;
+		if (!strcmp(arg, "--")) {
+			i++;
+			break;
+		}
+		if (!strcmp(arg, "-e") || !strcmp(arg, "--ed")) {
+			edarg++;
+			continue;
+		}
+		if (!strcmp(arg, "-E") || !strcmp(arg, "--show-overlap")) {
+			edarg++;
+			opts.markers = 1;
+			continue;
+		}
+		if (!strcmp(arg, "-A") || !strcmp(arg, "--show-all")) {
+			edarg++;
+			opts.show_all = 1;
+			opts.markers = 1;
+			continue;
+		}
+		if (!strcmp(arg, "-x") || !strcmp(arg, "--overlap-only")) {
+			edarg++;
+			opts.overlap_only = 1;
+			continue;
+		}
+		if (!strcmp(arg, "-X")) {
+			edarg++;
+			opts.overlap_only = 1;
+			opts.markers = 1;
+			continue;
+		}
+		if (!strcmp(arg, "-3") || !strcmp(arg, "--easy-only")) {
+			edarg++;
+			opts.unmerged_only = 1;
+			continue;
+		}
+		if (!strcmp(arg, "-m") || !strcmp(arg, "--merge")) {
+			opts.merge = 1;
+			continue;
+		}
+		if (!strcmp(arg, "-T") || !strcmp(arg, "--initial-tab")) {
+			opts.initial_tab = 1;
+			continue;
+		}
+		if (!strcmp(arg, "-L")) {
+			if ( argc <= i+1 )
+				die("-L needs a LABEL argument");
+			if ( label_count < 3 )
+				label_strings[label_count++] = argv[++i];
+			else
+				die("too many labels options specified");
+			continue;
+		}
+		if (!strncmp(arg, "--label=", 8)) {
+			if ( !*(arg+8) )
+				die("--label needs a LABEL argument");
+			if ( label_count < 3 )
+				label_strings[label_count++] = arg+8;
+			else
+				die("too many labels options specified");
+			continue;
+		}
+		if (!strncmp(arg, "--diff-program=", 15)) {
+			if ( !*(arg+15) )
+				die("--diff-program needs a PROGRAM argument");
+			opts.diff_prog = arg+15;
+			continue;
+		}
+		if (*(arg+1) != '-') {
+			const char *a = arg+1;
+
+			while (*a) {
+				switch (*a) {
+					case 'A':
+						edarg++;
+						opts.show_all = 1;
+						opts.markers = 1;
+						break;
+					case 'e':
+						edarg++;
+						break;
+					case 'E':
+						edarg++;
+						opts.markers = 1;
+						break;
+					case 'x':
+						edarg++;
+						opts.overlap_only = 1;
+						break;
+					case 'X':
+						edarg++;
+						opts.overlap_only = 1;
+						opts.markers = 1;
+						break;
+					case '3':
+						edarg++;
+						opts.unmerged_only = 1;
+						break;
+					case 'm':
+						opts.merge = 1;
+						break;
+					case 'T':
+						opts.initial_tab = 1;
+						break;
+					default:
+						error("unknown option: %c\n", *a);
+						usage(builtin_diff3_usage);
+						break;
+				}
+				a++;
+			}
+			continue;
+		}
+		error("unrecognised option: %s\n", arg);
+		usage(builtin_diff3_usage);
+	}
+
+	if (edarg > 1)
+		die("at most one of the AeExX3 options are allowed");
+
+	/* -AeExX3 without -m implies ed script */
+	opts.edscript = edarg && !opts.merge;
+	/* -m without -AeExX3 implies -A */
+	if (opts.merge && !edarg) {
+		opts.show_all = 1;
+		opts.markers = 1;
+	}
+
+	if (opts.edscript)
+		die("ed scripts not yet supported!");
+
+	if (argc != i+3) {
+		if ( argc < i+3 )
+			error("missing file argument(s)\n");
+		else
+			error("too many arguments ('%s'...)\n", argv[i+3]);
+		usage(builtin_diff3_usage);
+	}
+
+	file = &argv[i];
+
+	for ( i = label_count; i < 3; i++ )
+		label_strings[i] = file[i];
+
+	conflicts = diff3(stdout, file, label_strings, &opts);
+
+	return conflicts > 0;
+}
+
+
diff --git a/builtin.h b/builtin.h
index 26ebcaf..a0e9ab7 100644
--- a/builtin.h
+++ b/builtin.h
@@ -12,6 +12,8 @@ extern void help_unknown_cmd(const char 
 extern int cmd_help(int argc, const char **argv, const char *prefix);
 extern int cmd_version(int argc, const char **argv, const char *prefix);
 
+extern int cmd_diff3(int argc, const char **argv, const char *prefix);
+
 extern int cmd_whatchanged(int argc, const char **argv, const char *prefix);
 extern int cmd_show(int argc, const char **argv, const char *prefix);
 extern int cmd_log(int argc, const char **argv, const char *prefix);
diff --git a/git.c b/git.c
index 18ba14a..a76c7b6 100644
--- a/git.c
+++ b/git.c
@@ -233,6 +233,7 @@ static void handle_internal_command(int 
 		{ "format-patch", cmd_format_patch, NEEDS_PREFIX },
 		{ "count-objects", cmd_count_objects },
 		{ "diff", cmd_diff, NEEDS_PREFIX },
+		{ "diff3", cmd_diff3 },
 		{ "grep", cmd_grep, NEEDS_PREFIX },
 		{ "rm", cmd_rm, NEEDS_PREFIX },
 		{ "add", cmd_add, NEEDS_PREFIX },
diff --git a/t/txxxx-diff3.sh b/t/txxxx-diff3.sh
new file mode 100755
index 0000000..3b515e7
--- /dev/null
+++ b/t/txxxx-diff3.sh
@@ -0,0 +1,175 @@
+#!/bin/sh
+
+test_description='diff3 tests'
+
+. ./test-lib.sh
+
+###########################################################
+# initialise counts
+
+diff3_count=0
+diff3_fails=0
+diff3_stats=0
+total_count=0
+total_fails=0
+total_stats=0
+
+###########################################################
+# test that "git diff3" and "diff3" give the same output
+# and the same exit code for a given set of arguments.
+
+test_diff3 () {
+
+    diff3_count=$(expr $diff3_count + 1)
+
+    diff3 "$@" >out-diff3
+    dret="$?"
+
+    git diff3 "$@" >out-git
+    gret="$?"
+
+    diff out-diff3 out-git >out-diff 2>&1
+
+    if [ "$?" != 0 ]
+    then
+        diff3_fails=$(expr $diff3_fails + 1)
+        echo "*   FAIL: --- " "$@" " ---"
+    fi
+    if [ "$dret" != "$gret" ]
+    then
+        diff3_stats=$(expr $diff3_stats + 1)
+        echo "*   STAT: ($dret != $gret) --- " "$@" " ---"
+    fi
+
+}
+
+###########################################################
+# test that "git diff3" and "diff3" give the same output
+# and the same exit code for several invocations of git
+# with different external diff command lines. (ie "normal"
+# diff and several unified diffs with context lines 0->5)
+
+test_diff3_extra () {
+
+    diff3 "$@" >out-diff3
+    dret="$?"
+
+    for dopt in "" "-U0" "-U1" "-U2" "-u" "-U4" "-U5"
+    do
+        diff3_count=$(expr $diff3_count + 1)
+
+        git diff3 --diff-program="diff $dopt" "$@" >out-git
+        gret="$?"
+
+        diff out-diff3 out-git >out-diff 2>&1
+
+        if [ "$?" != 0 ]
+        then
+            diff3_fails=$(expr $diff3_fails + 1)
+            echo "*   FAIL: --- --diff-program='diff $dopt'" "$@" " ---"
+        fi
+        if [ "$dret" != "$gret" ]
+        then
+            diff3_stats=$(expr $diff3_stats + 1)
+            echo "*   STAT: ($dret != $gret) --- --diff-program='diff $dopt'" "$@" " ---"
+        fi
+    done
+
+}
+
+###########################################################
+# given some options ($1) and three input files ($2->$4)
+# test git diff3 on permutations of the input files.
+
+test_perm_files () {
+    test_diff3 $1 $2 $3 $4
+    test_diff3 $1 $4 $3 $2
+    test_diff3 $1 $3 $2 $4
+    test_diff3 $1 $4 $2 $3
+    test_diff3 $1 $2 $4 $3
+    test_diff3 $1 $3 $4 $2
+}
+
+###########################################################
+# given three input files ($1->$3) test git diff3 output
+# ($opt == "") and all merge types for several permutaions
+# of the input files.  also test the external diff support.
+
+test_files () {
+
+    diff3_count=0
+    diff3_fails=0
+    diff3_stats=0
+
+    for opt in "" "-mA" "-me" "-mE" "-mx" "-mX" "-m3"
+    do
+        test_perm_files $opt $1 $2 $3
+    done
+
+    test_diff3_extra -mA -L our                  $1 $2 $3
+    test_diff3_extra -mA -L our -L base          $1 $2 $3
+    test_diff3_extra -mA -L our -L base -L their $1 $2 $3
+
+    total_count=$(expr $total_count + $diff3_count)
+    total_fails=$(expr $total_fails + $diff3_fails)
+    total_stats=$(expr $total_stats + $diff3_stats)
+
+    fails_and_stats=$(expr $diff3_fails + $diff3_stats)
+
+    case "$fails_and_stats" in
+    0)
+        return 0 ;;
+    *)
+        echo "    tests: $diff3_count, fails: $diff3_fails, stats: $diff3_stats"
+        return 1 ;;
+    esac
+
+}
+
+###########################################################
+# ok so test some files.
+
+test_expect_success \
+    "test files lao tzu tao" \
+    'test_files ../txxxx/lao ../txxxx/tzu ../txxxx/tao'
+
+test_expect_success \
+    "test files lao1 tzu tao1" \
+    'test_files ../txxxx/lao1 ../txxxx/tzu ../txxxx/tao1'
+
+test_expect_success \
+    "test files lao2 tzu tao2" \
+    'test_files ../txxxx/lao2 ../txxxx/tzu ../txxxx/tao2'
+
+test_expect_success \
+    "test files lao3 tzu tao3" \
+    'test_files ../txxxx/lao3 ../txxxx/tzu ../txxxx/tao3'
+
+test_expect_success \
+    "test files lao4 tzu tao4" \
+    'test_files ../txxxx/lao4 ../txxxx/tzu ../txxxx/tao4'
+
+test_expect_success \
+    "test files tzu tzu tzu" \
+    'test_files ../txxxx/tzu ../txxxx/tzu ../txxxx/tzu'
+
+test_expect_success \
+    "test files lao tzu tzu" \
+    'test_files ../txxxx/lao ../txxxx/tzu ../txxxx/tzu'
+
+test_expect_success \
+    "test files lao tzu /dev/null" \
+    'test_files ../txxxx/lao ../txxxx/tzu /dev/null'
+
+test_expect_success \
+    "test files /dev/null tzu /dev/null" \
+    'test_files /dev/null ../txxxx/tzu /dev/null'
+
+###########################################################
+# ok we are done.
+
+echo "    tests: $total_count, fails: $total_fails, stats: $total_stats"
+
+test_done
+
+
diff --git a/t/txxxx/lao b/t/txxxx/lao
new file mode 100644
index 0000000..635ef2c
--- /dev/null
+++ b/t/txxxx/lao
@@ -0,0 +1,11 @@
+The Way that can be told of is not the eternal Way;
+The name that can be named is not the eternal name.
+The Nameless is the origin of Heaven and Earth;
+The Named is the mother of all things.
+Therefore let there always be non-being,
+  so we may see their subtlety,
+And let there always be being,
+  so we may see their outcome.
+The two are the same,
+But after they are produced,
+  they have different names.
diff --git a/t/txxxx/lao1 b/t/txxxx/lao1
new file mode 100644
index 0000000..831654a
--- /dev/null
+++ b/t/txxxx/lao1
@@ -0,0 +1,8 @@
+The Nameless is the origin of Heaven and Earth;
+
+  so we may see their subtlety,
+  so we may see their outcome.
+The two are the same,
+But after they are produced,
+They both may be called deep and profound.
+The door of all subtleties!
diff --git a/t/txxxx/lao2 b/t/txxxx/lao2
new file mode 100644
index 0000000..a7babdc
--- /dev/null
+++ b/t/txxxx/lao2
@@ -0,0 +1,16 @@
+The Nameless is the origin of Heaven and Earth;
+home, away
+The named is the mother of all things.
+
+Amy, Helen, Kym
+Therefore let there always be non-being,
+  so we may see their subtlety,
+fred, bill, george
+And let there always be being,
+  so we may see their outcome.
+The two are the same,
+But after they are produced,
+  they have different names.
+They both may be called deep and profound.
+Deeper and more profound,
+The door of all subtleties!
diff --git a/t/txxxx/lao3 b/t/txxxx/lao3
new file mode 100644
index 0000000..ea500e5
--- /dev/null
+++ b/t/txxxx/lao3
@@ -0,0 +1,13 @@
+The Nameless is the origin of Heaven and Earth;
+The named is the mother of ALL things.
+
+Therefore let there ALWAYS be non-being,
+  so we may see their subtlety,
+And let there ALWAYS be being,
+  so we may see their outcome.
+The two are the same,
+But after they are produced,
+  they have different names.
+They both may be called deep and profound.
+Deeper and more profound,
+The door of all subtleties!
diff --git a/t/txxxx/lao4 b/t/txxxx/lao4
new file mode 100644
index 0000000..2ca7ddd
--- /dev/null
+++ b/t/txxxx/lao4
@@ -0,0 +1,12 @@
+The Way that can be told of is not the eternal Way;
+The name that can be named is not the eternal name.
+The Nameless is the origin of Heaven and Earth;
+The Named is the mother of all things.
+Therefore let there always be non-being,
+  so we may see their subtlety,
+And let there always be being,
+  so we may see their outcome.
+The two are the same,
+But after they are produced,
+  they have different names.
+unfinished symphony
\ No newline at end of file
diff --git a/t/txxxx/tao b/t/txxxx/tao
new file mode 100644
index 0000000..ffe1ba3
--- /dev/null
+++ b/t/txxxx/tao
@@ -0,0 +1,14 @@
+The Way that can be told of is not the eternal Way;
+The name that can be named is not the eternal name.
+The Nameless is the origin of Heaven and Earth;
+The named is the mother of all things.
+
+Therefore let there always be non-being,
+  so we may see their subtlety,
+And let there always be being,
+  so we may see their result.
+The two are the same,
+But after they are produced,
+  they have different names.
+
+  -- The Way of Lao-Tzu, tr. Wing-tsit Chan
diff --git a/t/txxxx/tao1 b/t/txxxx/tao1
new file mode 100644
index 0000000..7c0e98a
--- /dev/null
+++ b/t/txxxx/tao1
@@ -0,0 +1,10 @@
+The Nameless is the origin of Heaven and Earth;
+The named is the mother of all things.
+Therefore let there always be non-being,
+And let there always be being,
+The two are the same,
+But after they are produced,
+  they have different names.
+They both may be called DEEP and profound.
+Deeper and more profound,
+The door of ALL subtleties!
diff --git a/t/txxxx/tao2 b/t/txxxx/tao2
new file mode 100644
index 0000000..033360b
--- /dev/null
+++ b/t/txxxx/tao2
@@ -0,0 +1,16 @@
+The Nameless is the origin of Heaven and Earth;
+The named is the mother of all things.
+mars, venus, neptune
+
+Therefore let there always be non-being,
+quartz, diamond, ruby
+  so we may see their subtlety,
+And let there always be being,
+lilly, rose, tulip
+  so we may see their outcome.
+The two are the same,
+But after they are produced,
+  they have different names.
+They both may be called deep and profound.
+Deeper and more profound,
+The door of all subtleties!
diff --git a/t/txxxx/tao3 b/t/txxxx/tao3
new file mode 100644
index 0000000..0457551
--- /dev/null
+++ b/t/txxxx/tao3
@@ -0,0 +1,13 @@
+The Nameless is the origin of Heaven and Earth;
+The named is the mother of all things.
+THEIR
+Therefore let there always be non-being,
+  so we may see THEIR subtlety,
+And let there always be being,
+  so we may see THEIR outcome.
+The two are the same,
+But after they are produced,
+  they have different names.
+They both may be called deep and profound.
+Deeper and more profound,
+The door of all subtleties!
diff --git a/t/txxxx/tao4 b/t/txxxx/tao4
new file mode 100644
index 0000000..fbe30fd
--- /dev/null
+++ b/t/txxxx/tao4
@@ -0,0 +1,15 @@
+The Way that can be told of is not the eternal Way;
+The name that can be named is not the eternal name.
+The Nameless is the origin of Heaven and Earth;
+The named is the mother of all things.
+
+Therefore let there always be non-being,
+  so we may see their subtlety,
+And let there always be being,
+  so we may see their result.
+The two are the same,
+But after they are produced,
+  they have different names.
+
+  -- The Way of Lao-Tzu, tr. Wing-tsit Chan
+unchained melody
\ No newline at end of file
diff --git a/t/txxxx/tzu b/t/txxxx/tzu
new file mode 100644
index 0000000..5af88a8
--- /dev/null
+++ b/t/txxxx/tzu
@@ -0,0 +1,13 @@
+The Nameless is the origin of Heaven and Earth;
+The named is the mother of all things.
+
+Therefore let there always be non-being,
+  so we may see their subtlety,
+And let there always be being,
+  so we may see their outcome.
+The two are the same,
+But after they are produced,
+  they have different names.
+They both may be called deep and profound.
+Deeper and more profound,
+The door of all subtleties!
-- 
1.4.2


^ permalink raw reply related

* filemode=false somewhat broken
From: Juergen Ruehle @ 2006-11-22 20:02 UTC (permalink / raw)
  To: git

Commit fd28b34afd9bbd58297a25edced3f504c9a5487a tried to ignore the
executable bit if filemode=false, but instead forced all files to be
regular with 644 permission bits nuking symlink support. The attached
diff works better for me, but note that I'm completely posix agnostic.

Unfortunately there still seems to be another problem somewhere: files
merged by octopus (wanted to try one, too, after the recent
discussions on the list:-) lose the x bits in the process. But as
usual I'm not smart enough to find the problem:-(

diff --git a/builtin-update-index.c b/builtin-update-index.c
index 7f9c638..f4b4bc4 100644
--- a/builtin-update-index.c
+++ b/builtin-update-index.c
@@ -112,13 +112,13 @@ static int add_file_to_cache(const char
 	ce->ce_mode = create_ce_mode(st.st_mode);
 	if (!trust_executable_bit) {
 		/* If there is an existing entry, pick the mode bits
-		 * from it, otherwise force to 644.
+		 * from it, otherwise use mask 666.
 		 */
 		int pos = cache_name_pos(path, namelen);
 		if (0 <= pos)
 			ce->ce_mode = active_cache[pos]->ce_mode;
 		else
-			ce->ce_mode = create_ce_mode(S_IFREG | 0644);
+			ce->ce_mode = create_ce_mode((S_IFMT | 0666) & st.st_mode);
 	}
 
 	if (index_path(ce->sha1, path, &st, !info_only))
diff --git a/read-cache.c b/read-cache.c
index 97c3867..6cbbecf 100644
--- a/read-cache.c
+++ b/read-cache.c
@@ -347,13 +347,13 @@ int add_file_to_index(const char *path,
 	ce->ce_mode = create_ce_mode(st.st_mode);
 	if (!trust_executable_bit) {
 		/* If there is an existing entry, pick the mode bits
-		 * from it, otherwise force to 644.
+		 * from it, otherwise use mask 666.
 		 */
 		int pos = cache_name_pos(path, namelen);
 		if (pos >= 0)
 			ce->ce_mode = active_cache[pos]->ce_mode;
 		else
-			ce->ce_mode = create_ce_mode(S_IFREG | 0644);
+			ce->ce_mode = create_ce_mode((S_IFMT | 0666) & st.st_mode);
 	}
 
 	if (index_path(ce->sha1, path, &st, 1))
-- 
1.4.4.g540c


^ permalink raw reply related

* Re: your mail
From: Davide Libenzi @ 2006-11-22 20:16 UTC (permalink / raw)
  To: Johannes Schindelin; +Cc: git
In-Reply-To: <Pine.LNX.4.63.0611212316080.26827@wbgn013.biozentrum.uni-wuerzburg.de>

On Tue, 21 Nov 2006, Johannes Schindelin wrote:

> [PATCH] xdiff: add xdl_merge()
> 
> This new function implements the functionality of RCS merge, but
> in-memory. It returns < 0 on error, otherwise the number of conflicts.
> 
> Finding the conflicting lines can be a very expensive task. You can
> control the eagerness of this algorithm:
> 
> - a level value of 0 means that all overlapping changes are treated
>   as conflicts,
> - a value of 1 means that if the overlapping changes are identical,
>   it is not treated as a conflict.
> - If you set level to 2, overlapping changes will be analyzed, so that
>   almost identical changes will not result in huge conflicts. Rather,
>   only the conflicting lines will be shown inside conflict markers.
> 
> With each increasing level, the algorithm gets slower, but more accurate.
> Note that the code for level 2 depends on the simple definition of
> mmfile_t specific to git, and therefore it will be harder to port that
> to LibXDiff.

Johannes, at the moment I'm chased by a huge storm of never ending emails, 
so I won't be able to follow up this one soon. A smart 3-way merge is in 
my plans for LibXDiff though.
There is quite some nice code around, that does pretty smart tricks and 
goes down to resolve sub-hunk non-trivial conflicts. You may want to take 
a look at that code too.



- Davide


^ permalink raw reply

* Re: StGit metadata grabbing with git clone
From: Robin Rosenberg @ 2006-11-22 20:29 UTC (permalink / raw)
  To: Otavio Salvador; +Cc: git
In-Reply-To: <87ac2jwutu.fsf@neumann.lab.ossystems.com.br>

onsdag 22 november 2006 13:05 skrev Otavio Salvador:
> Hello,
>
> I'm a happy user of stgit together with git to maintain a patch queue
> while I or the company team is working on patches that will be send
> for merging. Both works great but we're having troubles when we try to
> clone a stgit repository.
>
> When I clone the repository it grab the source but it loses the
> metadata. I would like to grab those too. Does anybody has a solution
> or a trick how I can do that?

You can copy the repo using rsync or scp instead of git-clone or use stg 
uncommit after the regular clone. 


^ permalink raw reply

* Re: A documentation to-do list
From: lamikr @ 2006-11-22 20:31 UTC (permalink / raw)
  To: Johannes Schindelin; +Cc: Chris Riddoch, git
In-Reply-To: <Pine.LNX.4.63.0611221044180.30004@wbgn013.biozentrum.uni-wuerzburg.de>

Johannes Schindelin wrote:
> Hi,
>
> On Tue, 21 Nov 2006, Chris Riddoch wrote:
>
>   
>> Having decided to take it on myself to improve Git's documentation, I
>> asked on #git if people had particular things they felt I should focus
>> on.
>>     
>
> I have a request, which is not about _what_ to document, but _how_. People 
> often complained about the bad introduction into git, pointing to 
> http://www.selenic.com/mercurial/wiki/index.cgi/QuickStart for a "way 
> better" tutorial.
>   
I agree with this. In addition at least I have always missed official
"home page" as even currently the kernel.org points for example just to

       http://www.kernel.org/pub/software/scm/git/
      
Ok, by clicking the "docs" subfolder one gets to man pages. But man
pages does not specify the basic things like, where is the official git
repository
and how to pull the latest official or development versions from there.
In addition man pages are not the fastest way to get started.
Instead small tutorial for example with a following kind of usage
scenario might be quite useful for many. (I do not know even myself to
step 10 :-)

1) One clones architehture specific git repository
    (for example git-clone
git://git.kernel.org/pub/scm/linux/kernel/git/tmlind/linux-omap-2.6.git)
2) This repository has omap specific things in master branch which is
often synced with main kernel
3) Once omap specific things are working agains the release kernel
(let's say 2.6.16), master is tagged with keys like
       "linux-omap-2.6.16-omap1"
4) User creates "MY_DEV" branch and adds own changes to there
5) User tags the branch with "MY_OMAP1_2_6_16" and releases own
2.6.16-omap1 based kernel
6) Master branch in OMAP is synced with the main kernel which is now
somewhere like 2.6.18-rc5
7) User changes to master branch, pulls the master branch to 2.6.18-rc5
level
8) User switches to MY_DEV branch has pulls it it 2.6.18-rc5 level from
master branch. Fix merge errors and commits them there.
9) Stable team releases bug fix version 2.6.16.25 to own git
10) User wants to release MY_OMAP1_2_6_16_25 version and would like to
use git-pull instead of using patch files
       - How to jump back to tagged version in repository?
       - How to pull the stable team changes here?

Other common issues that comes to my mind are but which are not easy to
find out from the current official
man based documentation:

1) where is the repository and gitweb for git itself. (only man pages
are easy to find out currently from net).
2) how to checkout the latest from there (even announcement emails does
not mention this currently!)
3) how to pull the git repository to newer version when Junio announces
new tar-balls
4) how to change to older tagged version (or to some older non tagged
commit version) and build from there
5) how to create own work branch, commit changes to there and
       a) use git-format-patches to create patch files
       b) automatize the patch sending via emails
       c) use push for sending changes back to master repository
6) what is the difference between origin and master. Can user push
changes to origin or should they always be pushed to master or own branch
7) how to create own repository
8) how to set-up gitweb to show your own git repository
9) how to allow others to pull over http connection from your repository
(this was for example easy, but it is hard to find any documentation
from this)
10) how to allow others to pull over git connection from your repository
(requires git-daemon + touch command with magic keyword)


^ permalink raw reply


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