Merge branch 'jc/date'
authorJunio C Hamano <junkio@cox.net>
Sat, 8 Apr 2006 01:00:06 +0000 (18:00 -0700)
committerJunio C Hamano <junkio@cox.net>
Sat, 8 Apr 2006 01:00:06 +0000 (18:00 -0700)
* jc/date:
  date parsing: be friendlier to our European friends.

16 files changed:
Documentation/git-imap-send.txt [new file with mode: 0644]
Documentation/technical/pack-format.txt [new file with mode: 0644]
Documentation/technical/pack-heuristics.txt [new file with mode: 0644]
Makefile
blame.c
combine-diff.c
commit.c
commit.h
delta.h
gitk
http-fetch.c
patch-delta.c
rev-list.c
sha1_file.c
xdiff-interface.c [new file with mode: 0644]
xdiff-interface.h [new file with mode: 0644]

diff --git a/Documentation/git-imap-send.txt b/Documentation/git-imap-send.txt
new file mode 100644 (file)
index 0000000..cfc0d88
--- /dev/null
@@ -0,0 +1,60 @@
+git-imap-send(1)
+================
+
+NAME
+----
+git-imap-send - Dump a mailbox from stdin into an imap folder
+
+
+SYNOPSIS
+--------
+'git-imap-send'
+
+
+DESCRIPTION
+-----------
+This command uploads a mailbox generated with git-format-patch
+into an imap drafts folder.  This allows patches to be sent as
+other email is sent with mail clients that cannot read mailbox
+files directly.
+
+Typical usage is something like:
+
+git-format-patch --signoff --stdout --attach origin | git-imap-send
+
+
+CONFIGURATION
+-------------
+
+git-imap-send requires the following values in the repository
+configuration file (shown with examples):
+
+[imap]
+    Folder = "INBOX.Drafts"
+
+[imap]
+    Tunnel = "ssh -q user@server.com /usr/bin/imapd ./Maildir 2> /dev/null"
+
+[imap]
+    Host = imap.server.com
+    User = bob
+    Password = pwd
+    Port = 143
+
+
+BUGS
+----
+Doesn't handle lines starting with "From " in the message body.
+
+
+Author
+------
+Derived from isync 1.0.1 by Mike McCormack.
+
+Documentation
+--------------
+Documentation by Mike McCormack
+
+GIT
+---
+Part of the gitlink:git[7] suite
diff --git a/Documentation/technical/pack-format.txt b/Documentation/technical/pack-format.txt
new file mode 100644 (file)
index 0000000..ed2decc
--- /dev/null
@@ -0,0 +1,111 @@
+GIT pack format
+===============
+
+= pack-*.pack file has the following format:
+
+   - The header appears at the beginning and consists of the following:
+
+     4-byte signature
+     4-byte version number (network byte order)
+     4-byte number of objects contained in the pack (network byte order)
+
+     Observation: we cannot have more than 4G versions ;-) and
+     more than 4G objects in a pack.
+
+   - The header is followed by number of object entries, each of
+     which looks like this:
+
+     (undeltified representation)
+     n-byte type and length (4-bit type, (n-1)*7+4-bit length)
+     compressed data
+
+     (deltified representation)
+     n-byte type and length (4-bit type, (n-1)*7+4-bit length)
+     20-byte base object name
+     compressed delta data
+
+     Observation: length of each object is encoded in a variable
+     length format and is not constrained to 32-bit or anything.
+
+  - The trailer records 20-byte SHA1 checksum of all of the above.
+
+= pack-*.idx file has the following format:
+
+  - The header consists of 256 4-byte network byte order
+    integers.  N-th entry of this table records the number of
+    objects in the corresponding pack, the first byte of whose
+    object name are smaller than N.  This is called the
+    'first-level fan-out' table.
+
+    Observation: we would need to extend this to an array of
+    8-byte integers to go beyond 4G objects per pack, but it is
+    not strictly necessary.
+
+  - The header is followed by sorted 28-byte entries, one entry
+    per object in the pack.  Each entry is:
+
+    4-byte network byte order integer, recording where the
+    object is stored in the packfile as the offset from the
+    beginning.
+
+    20-byte object name.
+
+    Observation: we would definitely need to extend this to
+    8-byte integer plus 20-byte object name to handle a packfile
+    that is larger than 4GB.
+
+  - The file is concluded with a trailer:
+
+    A copy of the 20-byte SHA1 checksum at the end of
+    corresponding packfile.
+
+    20-byte SHA1-checksum of all of the above.
+
+Pack Idx file:
+
+       idx
+           +--------------------------------+
+           | fanout[0] = 2                  |-.
+           +--------------------------------+ |
+           | fanout[1]                      | |
+           +--------------------------------+ |
+           | fanout[2]                      | |
+           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
+           | fanout[255]                    | |
+           +--------------------------------+ |
+main       | offset                         | |
+index      | object name 00XXXXXXXXXXXXXXXX | |
+table      +--------------------------------+ | 
+           | offset                         | |
+           | object name 00XXXXXXXXXXXXXXXX | |
+           +--------------------------------+ |
+         .-| offset                         |<+
+         | | object name 01XXXXXXXXXXXXXXXX |
+         | +--------------------------------+
+         | | offset                         |
+         | | object name 01XXXXXXXXXXXXXXXX |
+         | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+         | | offset                         |
+         | | object name FFXXXXXXXXXXXXXXXX |
+         | +--------------------------------+
+trailer          | | packfile checksum              |
+         | +--------------------------------+
+         | | idxfile checksum               |
+         | +--------------------------------+
+          .-------.      
+                  |
+Pack file entry: <+
+
+     packed object header:
+       1-byte type (upper 4-bit)
+              size0 (lower 4-bit) 
+        n-byte sizeN (as long as MSB is set, each 7-bit)
+               size0..sizeN form 4+7+7+..+7 bit integer, size0
+               is the most significant part.
+     packed object data:
+        If it is not DELTA, then deflated bytes (the size above
+               is the size before compression).
+       If it is DELTA, then
+         20-byte base object name SHA1 (the size above is the
+               size of the delta data that follows).
+          delta data, deflated.
diff --git a/Documentation/technical/pack-heuristics.txt b/Documentation/technical/pack-heuristics.txt
new file mode 100644 (file)
index 0000000..eaab3ee
--- /dev/null
@@ -0,0 +1,466 @@
+        Concerning Git's Packing Heuristics
+        ===================================
+
+        Oh, here's a really stupid question:
+
+                  Where do I go
+               to learn the details
+           of git's packing heuristics?
+
+Be careful what you ask!
+
+Followers of the git, please open the git IRC Log and turn to
+February 10, 2006.
+
+It's a rare occasion, and we are joined by the King Git Himself,
+Linus Torvalds (linus).  Nathaniel Smith, (njs`), has the floor
+and seeks enlightenment.  Others are present, but silent.
+
+Let's listen in!
+
+    <njs`> Oh, here's a really stupid question -- where do I go to
+        learn the details of git's packing heuristics?  google avails
+        me not, reading the source didn't help a lot, and wading
+        through the whole mailing list seems less efficient than any
+        of that.
+
+It is a bold start!  A plea for help combined with a simultaneous
+tri-part attack on some of the tried and true mainstays in the quest
+for enlightenment.  Brash accusations of google being useless. Hubris!
+Maligning the source.  Heresy!  Disdain for the mailing list archives.
+Woe.
+
+    <pasky> yes, the packing-related delta stuff is somewhat
+        mysterious even for me ;)
+
+Ah!  Modesty after all.
+
+    <linus> njs, I don't think the docs exist. That's something where
+        I don't think anybody else than me even really got involved.
+        Most of the rest of git others have been busy with (especially
+        Junio), but packing nobody touched after I did it.
+
+It's cryptic, yet vague.  Linus in style for sure.  Wise men
+interpret this as an apology.  A few argue it is merely a
+statement of fact.
+
+    <njs`> I guess the next step is "read the source again", but I
+        have to build up a certain level of gumption first :-)
+
+Indeed!  On both points.
+
+    <linus> The packing heuristic is actually really really simple.
+
+Bait...
+
+    <linus> But strange.
+
+And switch.  That ought to do it!
+
+    <linus> Remember: git really doesn't follow files. So what it does is
+        - generate a list of all objects
+        - sort the list according to magic heuristics
+        - walk the list, using a sliding window, seeing if an object
+          can be diffed against another object in the window
+        - write out the list in recency order
+
+The traditional understatement:
+
+    <njs`> I suspect that what I'm missing is the precise definition of
+        the word "magic"
+
+The traditional insight:
+
+    <pasky> yes
+
+And Bable-like confusion flowed.
+
+    <njs`> oh, hmm, and I'm not sure what this sliding window means either
+
+    <pasky> iirc, it appeared to me to be just the sha1 of the object
+        when reading the code casually ...
+
+        ... which simply doesn't sound as a very good heuristics, though ;)
+
+    <njs`> .....and recency order.  okay, I think it's clear I didn't
+       even realize how much I wasn't realizing :-)
+
+Ah, grasshopper!  And thus the enlightenment begins anew.
+
+    <linus> The "magic" is actually in theory totally arbitrary.
+        ANY order will give you a working pack, but no, it's not
+        ordered by SHA1.
+
+        Before talking about the ordering for the sliding delta
+        window, let's talk about the recency order. That's more
+        important in one way.
+
+    <njs`> Right, but if all you want is a working way to pack things
+        together, you could just use cat and save yourself some
+        trouble...
+
+Waaait for it....
+
+    <linus> The recency ordering (which is basically: put objects
+        _physically_ into the pack in the order that they are
+        "reachable" from the head) is important.
+
+    <njs`> okay
+
+    <linus> It's important because that's the thing that gives packs
+        good locality. It keeps the objects close to the head (whether
+        they are old or new, but they are _reachable_ from the head)
+        at the head of the pack. So packs actually have absolutely
+        _wonderful_ IO patterns.
+
+Read that again, because it is important.
+
+    <linus> But recency ordering is totally useless for deciding how
+        to actually generate the deltas, so the delta ordering is
+        something else.
+
+        The delta ordering is (wait for it):
+        - first sort by the "basename" of the object, as defined by
+          the name the object was _first_ reached through when
+          generating the object list
+        - within the same basename, sort by size of the object
+        - but always sort different types separately (commits first).
+
+        That's not exactly it, but it's very close.
+
+    <njs`> The "_first_ reached" thing is not too important, just you
+        need some way to break ties since the same objects may be
+        reachable many ways, yes?
+
+And as if to clarify:
+
+    <linus> The point is that it's all really just any random
+        heuristic, and the ordering is totally unimportant for
+        correctness, but it helps a lot if the heuristic gives
+        "clumping" for things that are likely to delta well against
+        each other.
+
+It is an important point, so secretly, I did my own research and have
+included my results below.  To be fair, it has changed some over time.
+And through the magic of Revisionistic History, I draw upon this entry
+from The Git IRC Logs on my father's birthday, March 1:
+
+    <gitster> The quote from the above linus should be rewritten a
+        bit (wait for it):
+        - first sort by type.  Different objects never delta with
+         each other.
+        - then sort by filename/dirname.  hash of the basename
+          occupies the top BITS_PER_INT-DIR_BITS bits, and bottom
+          DIR_BITS are for the hash of leading path elements.
+        - then if we are doing "thin" pack, the objects we are _not_
+          going to pack but we know about are sorted earlier than
+          other objects.
+        - and finally sort by size, larger to smaller.
+
+In one swell-foop, clarification and obscurification!  Nonetheless,
+authoritative.  Cryptic, yet concise.  It even solicits notions of
+quotes from The Source Code.  Clearly, more study is needed.
+
+    <gitster> That's the sort order.  What this means is:
+        - we do not delta different object types.
+       - we prefer to delta the objects with the same full path, but
+          allow files with the same name from different directories.
+       - we always prefer to delta against objects we are not going
+          to send, if there are some.
+       - we prefer to delta against larger objects, so that we have
+          lots of removals.
+
+        The penultimate rule is for "thin" packs.  It is used when
+        the other side is known to have such objects.
+
+There it is again. "Thin" packs.  I'm thinking to myself, "What
+is a 'thin' pack?"  So I ask:
+
+    <jdl> What is a "thin" pack?
+
+    <gitster> Use of --objects-edge to rev-list as the upstream of
+        pack-objects.  The pack transfer protocol negotiates that.
+
+Woo hoo!  Cleared that _right_ up!
+
+    <gitster> There are two directions - push and fetch.
+
+There!  Did you see it?  It is not '"push" and "pull"'!  How often the
+confusion has started here.  So casually mentioned, too!
+
+    <gitster> For push, git-send-pack invokes git-receive-pack on the
+        other end.  The receive-pack says "I have up to these commits".
+        send-pack looks at them, and computes what are missing from
+        the other end.  So "thin" could be the default there.
+
+        In the other direction, fetch, git-fetch-pack and
+        git-clone-pack invokes git-upload-pack on the other end
+       (via ssh or by talking to the daemon).
+
+       There are two cases: fetch-pack with -k and clone-pack is one,
+        fetch-pack without -k is the other.  clone-pack and fetch-pack
+        with -k will keep the downloaded packfile without expanded, so
+        we do not use thin pack transfer.  Otherwise, the generated
+        pack will have delta without base object in the same pack.
+
+        But fetch-pack without -k will explode the received pack into
+        individual objects, so we automatically ask upload-pack to
+        give us a thin pack if upload-pack supports it.
+
+OK then.
+
+Uh.
+
+Let's return to the previous conversation still in progress.
+
+    <njs`> and "basename" means something like "the tail of end of
+        path of file objects and dir objects, as per basename(3), and
+        we just declare all commit and tag objects to have the same
+        basename" or something?
+
+Luckily, that too is a point that gitster clarified for us!
+
+If I might add, the trick is to make files that _might_ be similar be
+located close to each other in the hash buckets based on their file
+names.  It used to be that "foo/Makefile", "bar/baz/quux/Makefile" and
+"Makefile" all landed in the same bucket due to their common basename,
+"Makefile". However, now they land in "close" buckets.
+
+The algorithm allows not just for the _same_ bucket, but for _close_
+buckets to be considered delta candidates.  The rationale is
+essentially that files, like Makefiles, often have very similar
+content no matter what directory they live in.
+
+    <linus> I played around with different delta algorithms, and with
+        making the "delta window" bigger, but having too big of a
+        sliding window makes it very expensive to generate the pack:
+        you need to compare every object with a _ton_ of other objects.
+
+        There are a number of other trivial heuristics too, which
+        basically boil down to "don't bother even trying to delta this
+        pair" if we can tell before-hand that the delta isn't worth it
+        (due to size differences, where we can take a previous delta
+        result into account to decide that "ok, no point in trying
+        that one, it will be worse").
+
+        End result: packing is actually very size efficient. It's
+        somewhat CPU-wasteful, but on the other hand, since you're
+        really only supposed to do it maybe once a month (and you can
+        do it during the night), nobody really seems to care.
+
+Nice Engineering Touch, there.  Find when it doesn't matter, and
+proclaim it a non-issue.  Good style too!
+
+    <njs`> So, just to repeat to see if I'm following, we start by
+        getting a list of the objects we want to pack, we sort it by
+        this heuristic (basically lexicographically on the tuple
+        (type, basename, size)).
+
+        Then we walk through this list, and calculate a delta of
+        each object against the last n (tunable paramater) objects,
+        and pick the smallest of these deltas.
+
+Vastly simplified, but the essence is there!
+
+    <linus> Correct.
+
+    <njs`> And then once we have picked a delta or fulltext to
+        represent each object, we re-sort by recency, and write them
+        out in that order.
+
+    <linus> Yup. Some other small details:
+
+And of course there is the "Other Shoe" Factor too.
+
+    <linus> - We limit the delta depth to another magic value (right
+        now both the window and delta depth magic values are just "10")
+
+    <njs`> Hrm, my intuition is that you'd end up with really _bad_ IO
+        patterns, because the things you want are near by, but to
+        actually reconstruct them you may have to jump all over in
+        random ways.
+
+    <linus> - When we write out a delta, and we haven't yet written
+        out the object it is a delta against, we write out the base
+        object first.  And no, when we reconstruct them, we actually
+        get nice IO patterns, because:
+        - larger objects tend to be "more recent" (Linus' law: files grow)
+        - we actively try to generate deltas from a larger object to a
+          smaller one
+        - this means that the top-of-tree very seldom has deltas
+          (ie deltas in _practice_ are "backwards deltas")
+
+Again, we should reread that whole paragraph.  Not just because
+Linus has slipped Linus's Law in there on us, but because it is
+important.  Let's make sure we clarify some of the points here:
+
+    <njs`> So the point is just that in practice, delta order and
+        recency order match each other quite well.
+
+    <linus> Yes. There's another nice side to this (and yes, it was
+       designed that way ;):
+        - the reason we generate deltas against the larger object is
+         actually a big space saver too!
+
+    <njs`> Hmm, but your last comment (if "we haven't yet written out
+        the object it is a delta against, we write out the base object
+        first"), seems like it would make these facts mostly
+        irrelevant because even if in practice you would not have to
+        wander around much, in fact you just brute-force say that in
+        the cases where you might have to wander, don't do that :-)
+
+    <linus> Yes and no. Notice the rule: we only write out the base
+        object first if the delta against it was more recent.  That
+        means that you can actually have deltas that refer to a base
+        object that is _not_ close to the delta object, but that only
+        happens when the delta is needed to generate an _old_ object.
+
+    <linus> See?
+
+Yeah, no.  I missed that on the first two or three readings myself.
+
+    <linus> This keeps the front of the pack dense. The front of the
+        pack never contains data that isn't relevant to a "recent"
+        object.  The size optimization comes from our use of xdelta
+        (but is true for many other delta algorithms): removing data
+        is cheaper (in size) than adding data.
+
+        When you remove data, you only need to say "copy bytes n--m".
+       In contrast, in a delta that _adds_ data, you have to say "add
+        these bytes: 'actual data goes here'"
+
+    *** njs` has quit: Read error: 104 (Connection reset by peer)
+
+    <linus> Uhhuh. I hope I didn't blow njs` mind.
+
+    *** njs` has joined channel #git
+
+    <pasky> :)
+
+The silent observers are amused.  Of course.
+
+And as if njs` was expected to be omniscient:
+
+    <linus> njs - did you miss anything?
+
+OK, I'll spell it out.  That's Geek Humor.  If njs` was not actually
+connected for a little bit there, how would he know if missed anything
+while he was disconnected?  He's a benevolent dictator with a sense of
+humor!  Well noted!
+
+    <njs`> Stupid router.  Or gremlins, or whatever.
+
+It's a cheap shot at Cisco.  Take 'em when you can.
+
+    <njs`> Yes and no. Notice the rule: we only write out the base
+        object first if the delta against it was more recent.
+
+        I'm getting lost in all these orders, let me re-read :-)
+       So the write-out order is from most recent to least recent?
+        (Conceivably it could be the opposite way too, I'm not sure if
+        we've said) though my connection back at home is logging, so I
+        can just read what you said there :-)
+
+And for those of you paying attention, the Omniscient Trick has just
+been detailed!
+
+    <linus> Yes, we always write out most recent first
+
+For the other record:
+
+    <pasky> njs`: http://pastebin.com/547965
+
+The 'net never forgets, so that should be good until the end of time.
+
+    <njs`> And, yeah, I got the part about deeper-in-history stuff
+        having worse IO characteristics, one sort of doesn't care.
+
+    <linus> With the caveat that if the "most recent" needs an older
+        object to delta against (hey, shrinking sometimes does
+        happen), we write out the old object with the delta.
+
+    <njs`> (if only it happened more...)
+
+    <linus> Anyway, the pack-file could easily be denser still, but
+        because it's used both for streaming (the git protocol) and
+        for on-disk, it has a few pessimizations.
+
+Actually, it is a made-up word. But it is a made-up word being
+used as setup for a later optimization, which is a real word:
+
+    <linus> In particular, while the pack-file is then compressed,
+        it's compressed just one object at a time, so the actual
+        compression factor is less than it could be in theory. But it
+        means that it's all nice random-access with a simple index to
+        do "object name->location in packfile" translation.
+
+    <njs`> I'm assuming the real win for delta-ing large->small is
+        more homogenous statistics for gzip to run over?
+
+        (You have to put the bytes in one place or another, but
+        putting them in a larger blob wins on compression)
+
+        Actually, what is the compression strategy -- each delta
+        individually gzipped, the whole file gzipped, somewhere in
+        between, no compression at all, ....?
+
+        Right.
+
+Reality IRC sets in.  For example:
+
+    <pasky> I'll read the rest in the morning, I really have to go
+        sleep or there's no hope whatsoever for me at the today's
+        exam... g'nite all.
+
+Heh.
+
+    <linus> pasky: g'nite
+
+    <njs`> pasky: 'luck
+
+    <linus> Right: large->small matters exactly because of compression
+        behaviour. If it was non-compressed, it probably wouldn't make
+        any difference.
+
+    <njs`> yeah
+
+    <linus> Anyway: I'm not even trying to claim that the pack-files
+        are perfect, but they do tend to have a nice balance of
+        density vs ease-of use.
+
+Gasp!  OK, saved.  That's a fair Engineering trade off.  Close call!
+In fact, Linus reflects on some Basic Engineering Fundamentals,
+design options, etc.
+
+    <linus> More importantly, they allow git to still _conceptually_
+        never deal with deltas at all, and be a "whole object" store.
+
+        Which has some problems (we discussed bad huge-file
+        behaviour on the git lists the other day), but it does mean
+        that the basic git concepts are really really simple and
+        straightforward.
+
+        It's all been quite stable.
+
+        Which I think is very much a result of having very simple
+        basic ideas, so that there's never any confusion about what's
+        going on.
+
+        Bugs happen, but they are "simple" bugs. And bugs that
+        actually get some object store detail wrong are almost always
+        so obious that they never go anywhere.
+
+    <njs`> Yeah.
+
+Nuff said.
+
+    <linus> Anyway.  I'm off for bed. It's not 6AM here, but I've got
+        three kids, and have to get up early in the morning to send
+        them off. I need my beauty sleep.
+
+    <njs`> :-)
+
+    <njs`> appreciate the infodump, I really was failing to find the
+        details on git packs :-)
+
+And now you know the rest of the story.
index 3367b8c..6b10eaa 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -208,7 +208,7 @@ LIB_OBJS = \
        quote.o read-cache.o refs.o run-command.o \
        server-info.o setup.o sha1_file.o sha1_name.o strbuf.o \
        tag.o tree.o usage.o config.o environment.o ctype.o copy.o \
-       fetch-clone.o revision.o pager.o tree-walk.o \
+       fetch-clone.o revision.o pager.o tree-walk.o xdiff-interface.o \
        $(DIFF_OBJS)
 
 GITLIBS = $(LIB_FILE) $(XDIFF_LIB)
@@ -324,10 +324,12 @@ ifndef NO_CURL
        curl_check := $(shell (echo 070908; curl-config --vernum) | sort -r | sed -ne 2p)
        ifeq "$(curl_check)" "070908"
                ifndef NO_EXPAT
-                       EXPAT_LIBEXPAT = -lexpat
                        PROGRAMS += git-http-push$X
                endif
        endif
+       ifndef NO_EXPAT
+               EXPAT_LIBEXPAT = -lexpat
+       endif
 endif
 
 ifndef NO_OPENSSL
@@ -513,6 +515,11 @@ exec_cmd.o: exec_cmd.c
 http.o: http.c
        $(CC) -o $*.o -c $(ALL_CFLAGS) -DGIT_USER_AGENT='"git/$(GIT_VERSION)"' $<
 
+ifdef NO_EXPAT
+http-fetch.o: http-fetch.c
+       $(CC) -o $*.o -c $(ALL_CFLAGS) -DNO_EXPAT $<
+endif
+
 git-%$X: %.o $(GITLIBS)
        $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS)
 
@@ -535,7 +542,7 @@ git-imap-send$X: imap-send.o $(LIB_FILE)
 
 git-http-fetch$X: fetch.o http.o http-fetch.o $(LIB_FILE)
        $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
-               $(LIBS) $(CURL_LIBCURL)
+               $(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT)
 
 git-http-push$X: revision.o http.o http-push.o $(LIB_FILE)
        $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
diff --git a/blame.c b/blame.c
index 98f9992..6730b10 100644 (file)
--- a/blame.c
+++ b/blame.c
@@ -16,6 +16,7 @@
 #include "diff.h"
 #include "diffcore.h"
 #include "revision.h"
+#include "xdiff-interface.h"
 
 #define DEBUG 0
 
@@ -57,116 +58,89 @@ static int num_get_patch = 0;
 static int num_commits = 0;
 static int patch_time = 0;
 
-#define TEMPFILE_PATH_LEN 60
-static struct patch *get_patch(struct commit *commit, struct commit *other)
-{
+struct blame_diff_state {
+       struct xdiff_emit_state xm;
        struct patch *ret;
-       struct util_info *info_c = (struct util_info *)commit->object.util;
-       struct util_info *info_o = (struct util_info *)other->object.util;
-       char tmp_path1[TEMPFILE_PATH_LEN], tmp_path2[TEMPFILE_PATH_LEN];
-       char diff_cmd[TEMPFILE_PATH_LEN*2 + 20];
-       struct timeval tv_start, tv_end;
-       int fd;
-       FILE *fin;
-       char buf[1024];
-
-       ret = xmalloc(sizeof(struct patch));
-       ret->chunks = NULL;
-       ret->num = 0;
-
-       get_blob(commit);
-       get_blob(other);
+};
 
-       gettimeofday(&tv_start, NULL);
+static void process_u0_diff(void *state_, char *line, unsigned long len)
+{
+       struct blame_diff_state *state = state_;
+       struct chunk *chunk;
 
-       fd = git_mkstemp(tmp_path1, TEMPFILE_PATH_LEN, "git-blame-XXXXXX");
-       if (fd < 0)
-               die("unable to create temp-file: %s", strerror(errno));
+       if (len < 4 || line[0] != '@' || line[1] != '@')
+               return;
 
-       if (xwrite(fd, info_c->buf, info_c->size) != info_c->size)
-               die("write failed: %s", strerror(errno));
-       close(fd);
+       if (DEBUG)
+               printf("chunk line: %.*s", (int)len, line);
+       state->ret->num++;
+       state->ret->chunks = xrealloc(state->ret->chunks,
+                                     sizeof(struct chunk) * state->ret->num);
+       chunk = &state->ret->chunks[state->ret->num - 1];
+
+       assert(!strncmp(line, "@@ -", 4));
+
+       if (parse_hunk_header(line, len,
+                             &chunk->off1, &chunk->len1,
+                             &chunk->off2, &chunk->len2)) {
+               state->ret->num--;
+               return;
+       }
 
-       fd = git_mkstemp(tmp_path2, TEMPFILE_PATH_LEN, "git-blame-XXXXXX");
-       if (fd < 0)
-               die("unable to create temp-file: %s", strerror(errno));
+       if (chunk->len1 == 0)
+               chunk->off1++;
+       if (chunk->len2 == 0)
+               chunk->off2++;
 
-       if (xwrite(fd, info_o->buf, info_o->size) != info_o->size)
-               die("write failed: %s", strerror(errno));
-       close(fd);
+       if (chunk->off1 > 0)
+               chunk->off1--;
+       if (chunk->off2 > 0)
+               chunk->off2--;
 
-       sprintf(diff_cmd, "diff -U 0 %s %s", tmp_path1, tmp_path2);
-       fin = popen(diff_cmd, "r");
-       if (!fin)
-               die("popen failed: %s", strerror(errno));
+       assert(chunk->off1 >= 0);
+       assert(chunk->off2 >= 0);
+}
 
-       while (fgets(buf, sizeof(buf), fin)) {
-               struct chunk *chunk;
-               char *start, *sp;
+static struct patch *get_patch(struct commit *commit, struct commit *other)
+{
+       struct blame_diff_state state;
+       xpparam_t xpp;
+       xdemitconf_t xecfg;
+       mmfile_t file_c, file_o;
+       xdemitcb_t ecb;
+       struct util_info *info_c = (struct util_info *)commit->object.util;
+       struct util_info *info_o = (struct util_info *)other->object.util;
+       struct timeval tv_start, tv_end;
 
-               if (buf[0] != '@' || buf[1] != '@')
-                       continue;
+       get_blob(commit);
+       file_c.ptr = info_c->buf;
+       file_c.size = info_c->size;
 
-               if (DEBUG)
-                       printf("chunk line: %s", buf);
-               ret->num++;
-               ret->chunks = xrealloc(ret->chunks,
-                                      sizeof(struct chunk) * ret->num);
-               chunk = &ret->chunks[ret->num - 1];
-
-               assert(!strncmp(buf, "@@ -", 4));
-
-               start = buf + 4;
-               sp = index(start, ' ');
-               *sp = '\0';
-               if (index(start, ',')) {
-                       int ret =
-                           sscanf(start, "%d,%d", &chunk->off1, &chunk->len1);
-                       assert(ret == 2);
-               } else {
-                       int ret = sscanf(start, "%d", &chunk->off1);
-                       assert(ret == 1);
-                       chunk->len1 = 1;
-               }
-               *sp = ' ';
-
-               start = sp + 1;
-               sp = index(start, ' ');
-               *sp = '\0';
-               if (index(start, ',')) {
-                       int ret =
-                           sscanf(start, "%d,%d", &chunk->off2, &chunk->len2);
-                       assert(ret == 2);
-               } else {
-                       int ret = sscanf(start, "%d", &chunk->off2);
-                       assert(ret == 1);
-                       chunk->len2 = 1;
-               }
-               *sp = ' ';
+       get_blob(other);
+       file_o.ptr = info_o->buf;
+       file_o.size = info_o->size;
 
-               if (chunk->len1 == 0)
-                       chunk->off1++;
-               if (chunk->len2 == 0)
-                       chunk->off2++;
+       gettimeofday(&tv_start, NULL);
 
-               if (chunk->off1 > 0)
-                       chunk->off1--;
-               if (chunk->off2 > 0)
-                       chunk->off2--;
+       xpp.flags = XDF_NEED_MINIMAL;
+       xecfg.ctxlen = 0;
+       xecfg.flags = 0;
+       ecb.outf = xdiff_outf;
+       ecb.priv = &state;
+       memset(&state, 0, sizeof(state));
+       state.xm.consume = process_u0_diff;
+       state.ret = xmalloc(sizeof(struct patch));
+       state.ret->chunks = NULL;
+       state.ret->num = 0;
 
-               assert(chunk->off1 >= 0);
-               assert(chunk->off2 >= 0);
-       }
-       pclose(fin);
-       unlink(tmp_path1);
-       unlink(tmp_path2);
+       xdl_diff(&file_c, &file_o, &xpp, &xecfg, &ecb);
 
        gettimeofday(&tv_end, NULL);
        patch_time += 1000000 * (tv_end.tv_sec - tv_start.tv_sec) +
                tv_end.tv_usec - tv_start.tv_usec;
 
        num_get_patch++;
-       return ret;
+       return state.ret;
 }
 
 static void free_patch(struct patch *p)
@@ -674,7 +648,7 @@ static void get_commit_info(struct commit* commit, struct commit_info* ret)
        static char author_buf[1024];
 
        tmp = strstr(commit->buffer, "\nauthor ") + 8;
-       len = index(tmp, '\n') - tmp;
+       len = strchr(tmp, '\n') - tmp;
        ret->author = author_buf;
        memcpy(ret->author, tmp, len);
 
@@ -729,11 +703,30 @@ static void* topo_getter(struct commit* c)
        return util->topo_data;
 }
 
+static int read_ancestry(const char *graft_file,
+                        unsigned char **start_sha1)
+{
+       FILE *fp = fopen(graft_file, "r");
+       char buf[1024];
+       if (!fp)
+               return -1;
+       while (fgets(buf, sizeof(buf), fp)) {
+               /* The format is just "Commit Parent1 Parent2 ...\n" */
+               int len = strlen(buf);
+               struct commit_graft *graft = read_graft_line(buf, len);
+               register_commit_graft(graft, 0);
+               if (!*start_sha1)
+                       *start_sha1 = graft->sha1;
+       }
+       fclose(fp);
+       return 0;
+}
+
 int main(int argc, const char **argv)
 {
        int i;
        struct commit *initial = NULL;
-       unsigned char sha1[20];
+       unsigned char sha1[20], *sha1_p = NULL;
 
        const char *filename = NULL, *commit = NULL;
        char filename_buf[256];
@@ -767,6 +760,14 @@ int main(int argc, const char **argv)
                                  !strcmp(argv[i], "--compability")) {
                                compability = 1;
                                continue;
+                       } else if(!strcmp(argv[i], "-S")) {
+                               if (i + 1 < argc &&
+                                   !read_ancestry(argv[i + 1], &sha1_p)) {
+                                       compability = 1;
+                                       i++;
+                                       continue;
+                               }
+                               usage(blame_usage);
                        } else if(!strcmp(argv[i], "--")) {
                                options = 0;
                                continue;
@@ -788,7 +789,9 @@ int main(int argc, const char **argv)
 
        if(!filename)
                usage(blame_usage);
-       if(!commit)
+       if (commit && sha1_p)
+               usage(blame_usage);
+       else if(!commit)
                commit = "HEAD";
 
        if(prefix)
@@ -797,9 +800,12 @@ int main(int argc, const char **argv)
                strcpy(filename_buf, filename);
        filename = filename_buf;
 
-       if (get_sha1(commit, sha1))
-               die("get_sha1 failed, commit '%s' not found", commit);
-       start_commit = lookup_commit_reference(sha1);
+       if (!sha1_p) {
+               if (get_sha1(commit, sha1))
+                       die("get_sha1 failed, commit '%s' not found", commit);
+               sha1_p = sha1;
+       }
+       start_commit = lookup_commit_reference(sha1_p);
        get_util(start_commit)->pathname = filename;
        if (fill_util_info(start_commit)) {
                printf("%s not found in %s\n", filename, commit);
@@ -813,6 +819,7 @@ int main(int argc, const char **argv)
        rev.prune_fn = simplify_commit;
        rev.topo_setter = topo_setter;
        rev.topo_getter = topo_getter;
+       rev.parents = 1;
        rev.limited = 1;
 
        commit_list_insert(start_commit, &rev.commits);
@@ -875,7 +882,7 @@ int main(int argc, const char **argv)
                        if(blame_contents[blame_len-1] != '\n')
                                putc('\n', stdout);
                } else {
-                       char* next_buf = index(buf, '\n') + 1;
+                       char* next_buf = strchr(buf, '\n') + 1;
                        fwrite(buf, next_buf - buf, 1, stdout);
                        buf = next_buf;
                }
index 7693884..eb0d757 100644 (file)
@@ -4,6 +4,7 @@
 #include "diff.h"
 #include "diffcore.h"
 #include "quote.h"
+#include "xdiff-interface.h"
 
 static int uninteresting(struct diff_filepair *p)
 {
@@ -110,78 +111,9 @@ static char *grab_blob(const unsigned char *sha1, unsigned long *size)
        return blob;
 }
 
-#define TMPPATHLEN 50
-#define MAXLINELEN 10240
-
-static void write_to_temp_file(char *tmpfile, void *blob, unsigned long size)
-{
-       int fd = git_mkstemp(tmpfile, TMPPATHLEN, ".diff_XXXXXX");
-       if (fd < 0)
-               die("unable to create temp-file");
-       if (write(fd, blob, size) != size)
-               die("unable to write temp-file");
-       close(fd);
-}
-
-static void write_temp_blob(char *tmpfile, const unsigned char *sha1)
-{
-       unsigned long size;
-       void *blob;
-       blob = grab_blob(sha1, &size);
-       write_to_temp_file(tmpfile, blob, size);
-       free(blob);
-}
-
-static int parse_num(char **cp_p, unsigned int *num_p)
-{
-       char *cp = *cp_p;
-       unsigned int num = 0;
-       int read_some;
-
-       while ('0' <= *cp && *cp <= '9')
-               num = num * 10 + *cp++ - '0';
-       if (!(read_some = cp - *cp_p))
-               return -1;
-       *cp_p = cp;
-       *num_p = num;
-       return 0;
-}
-
-static int parse_hunk_header(char *line, int len,
-                            unsigned int *ob, unsigned int *on,
-                            unsigned int *nb, unsigned int *nn)
-{
-       char *cp;
-       cp = line + 4;
-       if (parse_num(&cp, ob)) {
-       bad_line:
-               return error("malformed diff output: %s", line);
-       }
-       if (*cp == ',') {
-               cp++;
-               if (parse_num(&cp, on))
-                       goto bad_line;
-       }
-       else
-               *on = 1;
-       if (*cp++ != ' ' || *cp++ != '+')
-               goto bad_line;
-       if (parse_num(&cp, nb))
-               goto bad_line;
-       if (*cp == ',') {
-               cp++;
-               if (parse_num(&cp, nn))
-                       goto bad_line;
-       }
-       else
-               *nn = 1;
-       return -!!memcmp(cp, " @@", 3);
-}
-
-static void append_lost(struct sline *sline, int n, const char *line)
+static void append_lost(struct sline *sline, int n, const char *line, int len)
 {
        struct lline *lline;
-       int len = strlen(line);
        unsigned long this_mask = (1UL<<n);
        if (line[len-1] == '\n')
                len--;
@@ -216,70 +148,93 @@ static void append_lost(struct sline *sline, int n, const char *line)
        sline->lost_tail = &lline->next;
 }
 
-static void combine_diff(const unsigned char *parent, const char *ourtmp,
+struct combine_diff_state {
+       struct xdiff_emit_state xm;
+
+       unsigned int lno;
+       int ob, on, nb, nn;
+       unsigned long nmask;
+       int num_parent;
+       int n;
+       struct sline *sline;
+       struct sline *lost_bucket;
+};
+
+static void consume_line(void *state_, char *line, unsigned long len)
+{
+       struct combine_diff_state *state = state_;
+       if (5 < len && !memcmp("@@ -", line, 4)) {
+               if (parse_hunk_header(line, len,
+                                     &state->ob, &state->on,
+                                     &state->nb, &state->nn))
+                       return;
+               state->lno = state->nb;
+               if (!state->nb)
+                       /* @@ -1,2 +0,0 @@ to remove the
+                        * first two lines...
+                        */
+                       state->nb = 1;
+               if (state->nn == 0)
+                       /* @@ -X,Y +N,0 @@ removed Y lines
+                        * that would have come *after* line N
+                        * in the result.  Our lost buckets hang
+                        * to the line after the removed lines,
+                        */
+                       state->lost_bucket = &state->sline[state->nb];
+               else
+                       state->lost_bucket = &state->sline[state->nb-1];
+               if (!state->sline[state->nb-1].p_lno)
+                       state->sline[state->nb-1].p_lno =
+                               xcalloc(state->num_parent,
+                                       sizeof(unsigned long));
+               state->sline[state->nb-1].p_lno[state->n] = state->ob;
+               return;
+       }
+       if (!state->lost_bucket)
+               return; /* not in any hunk yet */
+       switch (line[0]) {
+       case '-':
+               append_lost(state->lost_bucket, state->n, line+1, len-1);
+               break;
+       case '+':
+               state->sline[state->lno-1].flag |= state->nmask;
+               state->lno++;
+               break;
+       }
+}
+
+static void combine_diff(const unsigned char *parent, mmfile_t *result_file,
                         struct sline *sline, int cnt, int n, int num_parent)
 {
-       FILE *in;
-       char parent_tmp[TMPPATHLEN];
-       char cmd[TMPPATHLEN * 2 + 1024];
-       char line[MAXLINELEN];
-       unsigned int lno, ob, on, nb, nn, p_lno;
+       unsigned int p_lno, lno;
        unsigned long nmask = (1UL << n);
-       struct sline *lost_bucket = NULL;
+       xpparam_t xpp;
+       xdemitconf_t xecfg;
+       mmfile_t parent_file;
+       xdemitcb_t ecb;
+       struct combine_diff_state state;
+       unsigned long sz;
 
        if (!cnt)
                return; /* result deleted */
 
-       write_temp_blob(parent_tmp, parent);
-       sprintf(cmd, "diff --unified=0 -La/x -Lb/x '%s' '%s'",
-               parent_tmp, ourtmp);
-       in = popen(cmd, "r");
-       if (!in)
-               die("cannot spawn %s", cmd);
-
-       lno = 1;
-       while (fgets(line, sizeof(line), in) != NULL) {
-               int len = strlen(line);
-               if (5 < len && !memcmp("@@ -", line, 4)) {
-                       if (parse_hunk_header(line, len,
-                                             &ob, &on, &nb, &nn))
-                               break;
-                       lno = nb;
-                       if (!nb)
-                               /* @@ -1,2 +0,0 @@ to remove the
-                                * first two lines...
-                                */
-                               nb = 1;
-                       if (nn == 0)
-                               /* @@ -X,Y +N,0 @@ removed Y lines
-                                * that would have come *after* line N
-                                * in the result.  Our lost buckets hang
-                                * to the line after the removed lines,
-                                */
-                               lost_bucket = &sline[nb];
-                       else
-                               lost_bucket = &sline[nb-1];
-                       if (!sline[nb-1].p_lno)
-                               sline[nb-1].p_lno =
-                                       xcalloc(num_parent,
-                                               sizeof(unsigned long));
-                       sline[nb-1].p_lno[n] = ob;
-                       continue;
-               }
-               if (!lost_bucket)
-                       continue; /* not in any hunk yet */
-               switch (line[0]) {
-               case '-':
-                       append_lost(lost_bucket, n, line+1);
-                       break;
-               case '+':
-                       sline[lno-1].flag |= nmask;
-                       lno++;
-                       break;
-               }
-       }
-       fclose(in);
-       unlink(parent_tmp);
+       parent_file.ptr = grab_blob(parent, &sz);
+       parent_file.size = sz;
+       xpp.flags = XDF_NEED_MINIMAL;
+       xecfg.ctxlen = 0;
+       xecfg.flags = 0;
+       ecb.outf = xdiff_outf;
+       ecb.priv = &state;
+       memset(&state, 0, sizeof(state));
+       state.xm.consume = consume_line;
+       state.nmask = nmask;
+       state.sline = sline;
+       state.lno = 1;
+       state.num_parent = num_parent;
+       state.n = n;
+
+       xdl_diff(&parent_file, result_file, &xpp, &xecfg, &ecb);
+       free(parent_file.ptr);
 
        /* Assign line numbers for this parent.
         *
@@ -625,61 +580,56 @@ static int show_patch_diff(struct combine_diff_path *elem, int num_parent,
                           int dense, const char *header,
                           struct diff_options *opt)
 {
-       unsigned long size, cnt, lno;
+       unsigned long result_size, cnt, lno;
        char *result, *cp, *ep;
        struct sline *sline; /* survived lines */
        int mode_differs = 0;
        int i, show_hunks, shown_header = 0;
-       char ourtmp_buf[TMPPATHLEN];
-       char *ourtmp = ourtmp_buf;
        int working_tree_file = !memcmp(elem->sha1, null_sha1, 20);
        int abbrev = opt->full_index ? 40 : DEFAULT_ABBREV;
+       mmfile_t result_file;
 
        /* Read the result of merge first */
-       if (!working_tree_file) {
-               result = grab_blob(elem->sha1, &size);
-               write_to_temp_file(ourtmp, result, size);
-       }
+       if (!working_tree_file)
+               result = grab_blob(elem->sha1, &result_size);
        else {
                /* Used by diff-tree to read from the working tree */
                struct stat st;
                int fd;
-               ourtmp = elem->path;
-               if (0 <= (fd = open(ourtmp, O_RDONLY)) &&
+               if (0 <= (fd = open(elem->path, O_RDONLY)) &&
                    !fstat(fd, &st)) {
                        int len = st.st_size;
                        int cnt = 0;
 
                        elem->mode = canon_mode(st.st_mode);
-                       size = len;
+                       result_size = len;
                        result = xmalloc(len + 1);
                        while (cnt < len) {
                                int done = xread(fd, result+cnt, len-cnt);
                                if (done == 0)
                                        break;
                                if (done < 0)
-                                       die("read error '%s'", ourtmp);
+                                       die("read error '%s'", elem->path);
                                cnt += done;
                        }
                        result[len] = 0;
                }
                else {
                        /* deleted file */
-                       size = 0;
+                       result_size = 0;
                        elem->mode = 0;
                        result = xmalloc(1);
                        result[0] = 0;
-                       ourtmp = "/dev/null";
                }
                if (0 <= fd)
                        close(fd);
        }
 
-       for (cnt = 0, cp = result; cp - result < size; cp++) {
+       for (cnt = 0, cp = result; cp - result < result_size; cp++) {
                if (*cp == '\n')
                        cnt++;
        }
-       if (size && result[size-1] != '\n')
+       if (result_size && result[result_size-1] != '\n')
                cnt++; /* incomplete line */
 
        sline = xcalloc(cnt+1, sizeof(*sline));
@@ -689,7 +639,7 @@ static int show_patch_diff(struct combine_diff_path *elem, int num_parent,
                sline[lno].lost_tail = &sline[lno].lost_head;
                sline[lno].flag = 0;
        }
-       for (lno = 0, cp = result; cp - result < size; cp++) {
+       for (lno = 0, cp = result; cp - result < result_size; cp++) {
                if (*cp == '\n') {
                        sline[lno].len = cp - sline[lno].bol;
                        lno++;
@@ -697,8 +647,11 @@ static int show_patch_diff(struct combine_diff_path *elem, int num_parent,
                                sline[lno].bol = cp + 1;
                }
        }
-       if (size && result[size-1] != '\n')
-               sline[cnt-1].len = size - (sline[cnt-1].bol - result);
+       if (result_size && result[result_size-1] != '\n')
+               sline[cnt-1].len = result_size - (sline[cnt-1].bol - result);
+
+       result_file.ptr = result;
+       result_file.size = result_size;
 
        sline[0].p_lno = xcalloc((cnt+1) * num_parent, sizeof(unsigned long));
        for (lno = 0; lno < cnt; lno++)
@@ -714,7 +667,7 @@ static int show_patch_diff(struct combine_diff_path *elem, int num_parent,
                        }
                }
                if (i <= j)
-                       combine_diff(elem->parent[i].sha1, ourtmp, sline,
+                       combine_diff(elem->parent[i].sha1, &result_file, sline,
                                     cnt, i, num_parent);
                if (elem->parent[i].mode != elem->mode)
                        mode_differs = 1;
@@ -767,8 +720,6 @@ static int show_patch_diff(struct combine_diff_path *elem, int num_parent,
                }
                dump_sline(sline, cnt, num_parent);
        }
-       if (ourtmp == ourtmp_buf)
-               unlink(ourtmp);
        free(result);
 
        for (i = 0; i < cnt; i++) {
index d4976fb..d534c9b 100644 (file)
--- a/commit.c
+++ b/commit.c
@@ -101,11 +101,7 @@ static unsigned long parse_commit_date(const char *buf)
        return date;
 }
 
-static struct commit_graft {
-       unsigned char sha1[20];
-       int nr_parent;
-       unsigned char parent[0][20]; /* more */
-} **commit_graft;
+static struct commit_graft **commit_graft;
 static int commit_graft_alloc, commit_graft_nr;
 
 static int commit_graft_pos(const unsigned char *sha1)
@@ -127,70 +123,98 @@ static int commit_graft_pos(const unsigned char *sha1)
        return -lo - 1;
 }
 
-static void prepare_commit_graft(void)
+int register_commit_graft(struct commit_graft *graft, int ignore_dups)
+{
+       int pos = commit_graft_pos(graft->sha1);
+       
+       if (0 <= pos) {
+               if (ignore_dups)
+                       free(graft);
+               else {
+                       free(commit_graft[pos]);
+                       commit_graft[pos] = graft;
+               }
+               return 1;
+       }
+       pos = -pos - 1;
+       if (commit_graft_alloc <= ++commit_graft_nr) {
+               commit_graft_alloc = alloc_nr(commit_graft_alloc);
+               commit_graft = xrealloc(commit_graft,
+                                       sizeof(*commit_graft) *
+                                       commit_graft_alloc);
+       }
+       if (pos < commit_graft_nr)
+               memmove(commit_graft + pos + 1,
+                       commit_graft + pos,
+                       (commit_graft_nr - pos - 1) *
+                       sizeof(*commit_graft));
+       commit_graft[pos] = graft;
+       return 0;
+}
+
+struct commit_graft *read_graft_line(char *buf, int len)
+{
+       /* The format is just "Commit Parent1 Parent2 ...\n" */
+       int i;
+       struct commit_graft *graft = NULL;
+
+       if (buf[len-1] == '\n')
+               buf[--len] = 0;
+       if (buf[0] == '#')
+               return 0;
+       if ((len + 1) % 41) {
+       bad_graft_data:
+               error("bad graft data: %s", buf);
+               free(graft);
+               return NULL;
+       }
+       i = (len + 1) / 41 - 1;
+       graft = xmalloc(sizeof(*graft) + 20 * i);
+       graft->nr_parent = i;
+       if (get_sha1_hex(buf, graft->sha1))
+               goto bad_graft_data;
+       for (i = 40; i < len; i += 41) {
+               if (buf[i] != ' ')
+                       goto bad_graft_data;
+               if (get_sha1_hex(buf + i + 1, graft->parent[i/41]))
+                       goto bad_graft_data;
+       }
+       return graft;
+}
+
+int read_graft_file(const char *graft_file)
 {
-       char *graft_file = get_graft_file();
        FILE *fp = fopen(graft_file, "r");
        char buf[1024];
-       if (!fp) {
-               commit_graft = (struct commit_graft **) "hack";
-               return;
-       }
+       if (!fp)
+               return -1;
        while (fgets(buf, sizeof(buf), fp)) {
                /* The format is just "Commit Parent1 Parent2 ...\n" */
                int len = strlen(buf);
-               int i;
-               struct commit_graft *graft = NULL;
-
-               if (buf[len-1] == '\n')
-                       buf[--len] = 0;
-               if (buf[0] == '#')
-                       continue;
-               if ((len + 1) % 41) {
-               bad_graft_data:
-                       error("bad graft data: %s", buf);
-                       free(graft);
-                       continue;
-               }
-               i = (len + 1) / 41 - 1;
-               graft = xmalloc(sizeof(*graft) + 20 * i);
-               graft->nr_parent = i;
-               if (get_sha1_hex(buf, graft->sha1))
-                       goto bad_graft_data;
-               for (i = 40; i < len; i += 41) {
-                       if (buf[i] != ' ')
-                               goto bad_graft_data;
-                       if (get_sha1_hex(buf + i + 1, graft->parent[i/41]))
-                               goto bad_graft_data;
-               }
-               i = commit_graft_pos(graft->sha1);
-               if (0 <= i) {
+               struct commit_graft *graft = read_graft_line(buf, len);
+               if (register_commit_graft(graft, 1))
                        error("duplicate graft data: %s", buf);
-                       free(graft);
-                       continue;
-               }
-               i = -i - 1;
-               if (commit_graft_alloc <= ++commit_graft_nr) {
-                       commit_graft_alloc = alloc_nr(commit_graft_alloc);
-                       commit_graft = xrealloc(commit_graft,
-                                               sizeof(*commit_graft) *
-                                               commit_graft_alloc);
-               }
-               if (i < commit_graft_nr)
-                       memmove(commit_graft + i + 1,
-                               commit_graft + i,
-                               (commit_graft_nr - i - 1) *
-                               sizeof(*commit_graft));
-               commit_graft[i] = graft;
        }
        fclose(fp);
+       return 0;
+}
+
+static void prepare_commit_graft(void)
+{
+       static int commit_graft_prepared;
+       char *graft_file;
+
+       if (commit_graft_prepared)
+               return;
+       graft_file = get_graft_file();
+       read_graft_file(graft_file);
+       commit_graft_prepared = 1;
 }
 
 static struct commit_graft *lookup_commit_graft(const unsigned char *sha1)
 {
        int pos;
-       if (!commit_graft)
-               prepare_commit_graft();
+       prepare_commit_graft();
        pos = commit_graft_pos(sha1);
        if (pos < 0)
                return NULL;
index 98682b2..918c9ab 100644 (file)
--- a/commit.h
+++ b/commit.h
@@ -90,4 +90,15 @@ void sort_in_topological_order(struct commit_list ** list, int lifo);
 void sort_in_topological_order_fn(struct commit_list ** list, int lifo,
                                  topo_sort_set_fn_t setter,
                                  topo_sort_get_fn_t getter);
+
+struct commit_graft {
+       unsigned char sha1[20];
+       int nr_parent;
+       unsigned char parent[FLEX_ARRAY][20]; /* more */
+};
+
+struct commit_graft *read_graft_line(char *buf, int len);
+int register_commit_graft(struct commit_graft *, int);
+int read_graft_file(const char *graft_file);
+
 #endif /* COMMIT_H */
diff --git a/delta.h b/delta.h
index a15350d..9464f3e 100644 (file)
--- a/delta.h
+++ b/delta.h
@@ -16,7 +16,8 @@ extern void *patch_delta(void *src_buf, unsigned long src_size,
  * This must be called twice on the delta data buffer, first to get the
  * expected reference buffer size, and again to get the result buffer size.
  */
-static inline unsigned long get_delta_hdr_size(const unsigned char **datap)
+static inline unsigned long get_delta_hdr_size(const unsigned char **datap,
+                                              const unsigned char *top)
 {
        const unsigned char *data = *datap;
        unsigned char cmd;
@@ -26,7 +27,7 @@ static inline unsigned long get_delta_hdr_size(const unsigned char **datap)
                cmd = *data++;
                size |= (cmd & ~0x80) << i;
                i += 7;
-       } while (cmd & 0x80);
+       } while (cmd & 0x80 && data < top);
        *datap = data;
        return size;
 }
diff --git a/gitk b/gitk
index 26fa79a..f88c06e 100755 (executable)
--- a/gitk
+++ b/gitk
@@ -2230,7 +2230,7 @@ proc donefilediff {} {
     }
 }
 
-proc findcont {id} {
+proc findcont {} {
     global findid treediffs parentlist
     global ffileline findstartline finddidsel
     global displayorder numcommits matchinglines findinprogress
@@ -2700,7 +2700,7 @@ proc getmergediffline {mdf id np} {
        incr nextupdate 100
        fileevent $mdf readable {}
        update
-       fileevent $mdf readable [list getmergediffline $mdf $id]
+       fileevent $mdf readable [list getmergediffline $mdf $id $np]
     }
 }
 
index dc67218..71a7daf 100644 (file)
@@ -4,6 +4,35 @@
 #include "fetch.h"
 #include "http.h"
 
+#ifndef NO_EXPAT
+#include <expat.h>
+
+/* Definitions for DAV requests */
+#define DAV_PROPFIND "PROPFIND"
+#define DAV_PROPFIND_RESP ".multistatus.response"
+#define DAV_PROPFIND_NAME ".multistatus.response.href"
+#define DAV_PROPFIND_COLLECTION ".multistatus.response.propstat.prop.resourcetype.collection"
+#define PROPFIND_ALL_REQUEST "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<D:propfind xmlns:D=\"DAV:\">\n<D:allprop/>\n</D:propfind>"
+
+/* Definitions for processing XML DAV responses */
+#ifndef XML_STATUS_OK
+enum XML_Status {
+  XML_STATUS_OK = 1,
+  XML_STATUS_ERROR = 0
+};
+#define XML_STATUS_OK    1
+#define XML_STATUS_ERROR 0
+#endif
+
+/* Flags that control remote_ls processing */
+#define PROCESS_FILES (1u << 0)
+#define PROCESS_DIRS  (1u << 1)
+#define RECURSIVE     (1u << 2)
+
+/* Flags that remote_ls passes to callback functions */
+#define IS_DIR (1u << 0)
+#endif
+
 #define PREV_BUF_SIZE 4096
 #define RANGE_HEADER_SIZE 30
 
@@ -15,6 +44,7 @@ static struct curl_slist *no_pragma_header;
 struct alt_base
 {
        char *base;
+       int path_len;
        int got_indices;
        struct packed_git *packs;
        struct alt_base *next;
@@ -58,6 +88,30 @@ struct alternates_request {
        int http_specific;
 };
 
+#ifndef NO_EXPAT
+struct xml_ctx
+{
+       char *name;
+       int len;
+       char *cdata;
+       void (*userFunc)(struct xml_ctx *ctx, int tag_closed);
+       void *userData;
+};
+
+struct remote_ls_ctx
+{
+       struct alt_base *repo;
+       char *path;
+       void (*userFunc)(struct remote_ls_ctx *ls);
+       void *userData;
+       int flags;
+       char *dentry_name;
+       int dentry_flags;
+       int rc;
+       struct remote_ls_ctx *parent;
+};
+#endif
+
 static struct object_request *object_queue_head = NULL;
 
 static size_t fwrite_sha1_file(void *ptr, size_t eltsize, size_t nmemb,
@@ -500,6 +554,7 @@ static void process_alternates_response(void *callback_data)
                        int serverlen = 0;
                        struct alt_base *newalt;
                        char *target = NULL;
+                       char *path;
                        if (data[i] == '/') {
                                serverlen = strchr(base + 8, '/') - base;
                                okay = 1;
@@ -540,6 +595,13 @@ static void process_alternates_response(void *callback_data)
                                newalt->base = target;
                                newalt->got_indices = 0;
                                newalt->packs = NULL;
+                               path = strstr(target, "//");
+                               if (path) {
+                                       path = index(path+2, '/');
+                                       if (path)
+                                               newalt->path_len = strlen(path);
+                               }
+
                                while (tail->next != NULL)
                                        tail = tail->next;
                                tail->next = newalt;
@@ -611,6 +673,209 @@ static void fetch_alternates(char *base)
        free(url);
 }
 
+#ifndef NO_EXPAT
+static void
+xml_start_tag(void *userData, const char *name, const char **atts)
+{
+       struct xml_ctx *ctx = (struct xml_ctx *)userData;
+       const char *c = index(name, ':');
+       int new_len;
+
+       if (c == NULL)
+               c = name;
+       else
+               c++;
+
+       new_len = strlen(ctx->name) + strlen(c) + 2;
+
+       if (new_len > ctx->len) {
+               ctx->name = xrealloc(ctx->name, new_len);
+               ctx->len = new_len;
+       }
+       strcat(ctx->name, ".");
+       strcat(ctx->name, c);
+
+       if (ctx->cdata) {
+               free(ctx->cdata);
+               ctx->cdata = NULL;
+       }
+
+       ctx->userFunc(ctx, 0);
+}
+
+static void
+xml_end_tag(void *userData, const char *name)
+{
+       struct xml_ctx *ctx = (struct xml_ctx *)userData;
+       const char *c = index(name, ':');
+       char *ep;
+
+       ctx->userFunc(ctx, 1);
+
+       if (c == NULL)
+               c = name;
+       else
+               c++;
+
+       ep = ctx->name + strlen(ctx->name) - strlen(c) - 1;
+       *ep = 0;
+}
+
+static void
+xml_cdata(void *userData, const XML_Char *s, int len)
+{
+       struct xml_ctx *ctx = (struct xml_ctx *)userData;
+       if (ctx->cdata)
+               free(ctx->cdata);
+       ctx->cdata = xcalloc(len+1, 1);
+       strncpy(ctx->cdata, s, len);
+}
+
+static int remote_ls(struct alt_base *repo, const char *path, int flags,
+                    void (*userFunc)(struct remote_ls_ctx *ls),
+                    void *userData);
+
+static void handle_remote_ls_ctx(struct xml_ctx *ctx, int tag_closed)
+{
+       struct remote_ls_ctx *ls = (struct remote_ls_ctx *)ctx->userData;
+
+       if (tag_closed) {
+               if (!strcmp(ctx->name, DAV_PROPFIND_RESP) && ls->dentry_name) {
+                       if (ls->dentry_flags & IS_DIR) {
+                               if (ls->flags & PROCESS_DIRS) {
+                                       ls->userFunc(ls);
+                               }
+                               if (strcmp(ls->dentry_name, ls->path) &&
+                                   ls->flags & RECURSIVE) {
+                                       ls->rc = remote_ls(ls->repo,
+                                                          ls->dentry_name,
+                                                          ls->flags,
+                                                          ls->userFunc,
+                                                          ls->userData);
+                               }
+                       } else if (ls->flags & PROCESS_FILES) {
+                               ls->userFunc(ls);
+                       }
+               } else if (!strcmp(ctx->name, DAV_PROPFIND_NAME) && ctx->cdata) {
+                       ls->dentry_name = xmalloc(strlen(ctx->cdata) -
+                                                 ls->repo->path_len + 1);
+                       strcpy(ls->dentry_name, ctx->cdata + ls->repo->path_len);
+               } else if (!strcmp(ctx->name, DAV_PROPFIND_COLLECTION)) {
+                       ls->dentry_flags |= IS_DIR;
+               }
+       } else if (!strcmp(ctx->name, DAV_PROPFIND_RESP)) {
+               if (ls->dentry_name) {
+                       free(ls->dentry_name);
+               }
+               ls->dentry_name = NULL;
+               ls->dentry_flags = 0;
+       }
+}
+
+static int remote_ls(struct alt_base *repo, const char *path, int flags,
+                    void (*userFunc)(struct remote_ls_ctx *ls),
+                    void *userData)
+{
+       char *url = xmalloc(strlen(repo->base) + strlen(path) + 1);
+       struct active_request_slot *slot;
+       struct slot_results results;
+       struct buffer in_buffer;
+       struct buffer out_buffer;
+       char *in_data;
+       char *out_data;
+       XML_Parser parser = XML_ParserCreate(NULL);
+       enum XML_Status result;
+       struct curl_slist *dav_headers = NULL;
+       struct xml_ctx ctx;
+       struct remote_ls_ctx ls;
+
+       ls.flags = flags;
+       ls.repo = repo;
+       ls.path = strdup(path);
+       ls.dentry_name = NULL;
+       ls.dentry_flags = 0;
+       ls.userData = userData;
+       ls.userFunc = userFunc;
+       ls.rc = 0;
+
+       sprintf(url, "%s%s", repo->base, path);
+
+       out_buffer.size = strlen(PROPFIND_ALL_REQUEST);
+       out_data = xmalloc(out_buffer.size + 1);
+       snprintf(out_data, out_buffer.size + 1, PROPFIND_ALL_REQUEST);
+       out_buffer.posn = 0;
+       out_buffer.buffer = out_data;
+
+       in_buffer.size = 4096;
+       in_data = xmalloc(in_buffer.size);
+       in_buffer.posn = 0;
+       in_buffer.buffer = in_data;
+
+       dav_headers = curl_slist_append(dav_headers, "Depth: 1");
+       dav_headers = curl_slist_append(dav_headers, "Content-Type: text/xml");
+
+       slot = get_active_slot();
+       slot->results = &results;
+       curl_easy_setopt(slot->curl, CURLOPT_INFILE, &out_buffer);
+       curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, out_buffer.size);
+       curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer);
+       curl_easy_setopt(slot->curl, CURLOPT_FILE, &in_buffer);
+       curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
+       curl_easy_setopt(slot->curl, CURLOPT_URL, url);
+       curl_easy_setopt(slot->curl, CURLOPT_UPLOAD, 1);
+       curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_PROPFIND);
+       curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers);
+
+       if (start_active_slot(slot)) {
+               run_active_slot(slot);
+               if (results.curl_result == CURLE_OK) {
+                       ctx.name = xcalloc(10, 1);
+                       ctx.len = 0;
+                       ctx.cdata = NULL;
+                       ctx.userFunc = handle_remote_ls_ctx;
+                       ctx.userData = &ls;
+                       XML_SetUserData(parser, &ctx);
+                       XML_SetElementHandler(parser, xml_start_tag,
+                                             xml_end_tag);
+                       XML_SetCharacterDataHandler(parser, xml_cdata);
+                       result = XML_Parse(parser, in_buffer.buffer,
+                                          in_buffer.posn, 1);
+                       free(ctx.name);
+
+                       if (result != XML_STATUS_OK) {
+                               ls.rc = error("XML error: %s",
+                                             XML_ErrorString(
+                                                     XML_GetErrorCode(parser)));
+                       }
+               } else {
+                       ls.rc = -1;
+               }
+       } else {
+               ls.rc = error("Unable to start PROPFIND request");
+       }
+
+       free(ls.path);
+       free(url);
+       free(out_data);
+       free(in_buffer.buffer);
+       curl_slist_free_all(dav_headers);
+
+       return ls.rc;
+}
+
+static void process_ls_pack(struct remote_ls_ctx *ls)
+{
+       unsigned char sha1[20];
+
+       if (strlen(ls->dentry_name) == 63 &&
+           !strncmp(ls->dentry_name, "objects/pack/pack-", 18) &&
+           !strncmp(ls->dentry_name+58, ".pack", 5)) {
+               get_sha1_hex(ls->dentry_name + 18, sha1);
+               setup_index(ls->repo, sha1);
+       }
+}
+#endif
+
 static int fetch_indices(struct alt_base *repo)
 {
        unsigned char sha1[20];
@@ -633,6 +898,12 @@ static int fetch_indices(struct alt_base *repo)
        if (get_verbosely)
                fprintf(stderr, "Getting pack list for %s\n", repo->base);
 
+#ifndef NO_EXPAT
+       if (remote_ls(repo, "objects/pack/", PROCESS_FILES,
+                     process_ls_pack, NULL) == 0)
+               return 0;
+#endif
+
        url = xmalloc(strlen(repo->base) + 21);
        sprintf(url, "%s/objects/info/packs", repo->base);
 
@@ -947,6 +1218,7 @@ int main(int argc, char **argv)
 {
        char *commit_id;
        char *url;
+       char *path;
        int arg = 1;
        int rc = 0;
 
@@ -987,6 +1259,12 @@ int main(int argc, char **argv)
        alt->got_indices = 0;
        alt->packs = NULL;
        alt->next = NULL;
+       path = strstr(url, "//");
+       if (path) {
+               path = index(path+2, '/');
+               if (path)
+                       alt->path_len = strlen(path);
+       }
 
        if (pull(commit_id))
                rc = 1;
index c0e1311..d95f0d9 100644 (file)
@@ -28,12 +28,12 @@ void *patch_delta(void *src_buf, unsigned long src_size,
        top = delta_buf + delta_size;
 
        /* make sure the orig file size matches what we expect */
-       size = get_delta_hdr_size(&data);
+       size = get_delta_hdr_size(&data, top);
        if (size != src_size)
                return NULL;
 
        /* now the result size */
-       size = get_delta_hdr_size(&data);
+       size = get_delta_hdr_size(&data, top);
        dst_buf = malloc(size + 1);
        if (!dst_buf)
                return NULL;
@@ -52,21 +52,37 @@ void *patch_delta(void *src_buf, unsigned long src_size,
                        if (cmd & 0x20) cp_size |= (*data++ << 8);
                        if (cmd & 0x40) cp_size |= (*data++ << 16);
                        if (cp_size == 0) cp_size = 0x10000;
+                       if (cp_off + cp_size < cp_size ||
+                           cp_off + cp_size > src_size ||
+                           cp_size > size)
+                               goto bad;
                        memcpy(out, src_buf + cp_off, cp_size);
                        out += cp_size;
-               } else {
+                       size -= cp_size;
+               } else if (cmd) {
+                       if (cmd > size)
+                               goto bad;
                        memcpy(out, data, cmd);
                        out += cmd;
                        data += cmd;
+                       size -= cmd;
+               } else {
+                       /*
+                        * cmd == 0 is reserved for future encoding
+                        * extensions. In the mean time we must fail when
+                        * encountering them (might be data corruption).
+                        */
+                       goto bad;
                }
        }
 
        /* sanity check */
-       if (data != top || out - dst_buf != size) {
+       if (data != top || size != 0) {
+               bad:
                free(dst_buf);
                return NULL;
        }
 
-       *dst_size = size;
+       *dst_size = out - dst_buf;
        return dst_buf;
 }
index 22141e2..1301502 100644 (file)
@@ -30,6 +30,7 @@ static const char rev_list_usage[] =
 "    --unpacked\n"
 "    --header | --pretty\n"
 "    --abbrev=nr | --no-abbrev\n"
+"    --abbrev-commit\n"
 "  special purpose:\n"
 "    --bisect"
 ;
@@ -39,6 +40,7 @@ struct rev_info revs;
 static int bisect_list = 0;
 static int verbose_header = 0;
 static int abbrev = DEFAULT_ABBREV;
+static int abbrev_commit = 0;
 static int show_timestamp = 0;
 static int hdr_termination = 0;
 static const char *commit_prefix = "";
@@ -52,7 +54,10 @@ static void show_commit(struct commit *commit)
                fputs(commit_prefix, stdout);
        if (commit->object.flags & BOUNDARY)
                putchar('-');
-       fputs(sha1_to_hex(commit->object.sha1), stdout);
+       if (abbrev_commit && abbrev)
+               fputs(find_unique_abbrev(commit->object.sha1, abbrev), stdout);
+       else
+               fputs(sha1_to_hex(commit->object.sha1), stdout);
        if (revs.parents) {
                struct commit_list *parents = commit->parents;
                while (parents) {
@@ -319,6 +324,14 @@ int main(int argc, const char **argv)
                        abbrev = 0;
                        continue;
                }
+               if (!strcmp(arg, "--abbrev")) {
+                       abbrev = DEFAULT_ABBREV;
+                       continue;
+               }
+               if (!strcmp(arg, "--abbrev-commit")) {
+                       abbrev_commit = 1;
+                       continue;
+               }
                if (!strncmp(arg, "--abbrev=", 9)) {
                        abbrev = strtoul(arg + 9, NULL, 10);
                        if (abbrev && abbrev < MINIMUM_ABBREV)
index ba8c4f7..e3d0113 100644 (file)
@@ -808,10 +808,12 @@ static int packed_delta_info(unsigned char *base_sha1,
                 * the result size.
                 */
                data = delta_head;
-               get_delta_hdr_size(&data); /* ignore base size */
+
+               /* ignore base size */
+               get_delta_hdr_size(&data, delta_head+sizeof(delta_head));
 
                /* Read the result size */
-               result_size = get_delta_hdr_size(&data);
+               result_size = get_delta_hdr_size(&data, delta_head+sizeof(delta_head));
                *sizep = result_size;
        }
        return 0;
diff --git a/xdiff-interface.c b/xdiff-interface.c
new file mode 100644 (file)
index 0000000..6a82da7
--- /dev/null
@@ -0,0 +1,104 @@
+#include "cache.h"
+#include "xdiff-interface.h"
+
+static int parse_num(char **cp_p, int *num_p)
+{
+       char *cp = *cp_p;
+       int num = 0;
+       int read_some;
+
+       while ('0' <= *cp && *cp <= '9')
+               num = num * 10 + *cp++ - '0';
+       if (!(read_some = cp - *cp_p))
+               return -1;
+       *cp_p = cp;
+       *num_p = num;
+       return 0;
+}
+
+int parse_hunk_header(char *line, int len,
+                     int *ob, int *on,
+                     int *nb, int *nn)
+{
+       char *cp;
+       cp = line + 4;
+       if (parse_num(&cp, ob)) {
+       bad_line:
+               return error("malformed diff output: %s", line);
+       }
+       if (*cp == ',') {
+               cp++;
+               if (parse_num(&cp, on))
+                       goto bad_line;
+       }
+       else
+               *on = 1;
+       if (*cp++ != ' ' || *cp++ != '+')
+               goto bad_line;
+       if (parse_num(&cp, nb))
+               goto bad_line;
+       if (*cp == ',') {
+               cp++;
+               if (parse_num(&cp, nn))
+                       goto bad_line;
+       }
+       else
+               *nn = 1;
+       return -!!memcmp(cp, " @@", 3);
+}
+
+static void consume_one(void *priv_, char *s, unsigned long size)
+{
+       struct xdiff_emit_state *priv = priv_;
+       char *ep;
+       while (size) {
+               unsigned long this_size;
+               ep = memchr(s, '\n', size);
+               this_size = (ep == NULL) ? size : (ep - s + 1);
+               priv->consume(priv, s, this_size);
+               size -= this_size;
+               s += this_size;
+       }
+}
+
+int xdiff_outf(void *priv_, mmbuffer_t *mb, int nbuf)
+{
+       struct xdiff_emit_state *priv = priv_;
+       int i;
+
+       for (i = 0; i < nbuf; i++) {
+               if (mb[i].ptr[mb[i].size-1] != '\n') {
+                       /* Incomplete line */
+                       priv->remainder = realloc(priv->remainder,
+                                                 priv->remainder_size +
+                                                 mb[i].size);
+                       memcpy(priv->remainder + priv->remainder_size,
+                              mb[i].ptr, mb[i].size);
+                       priv->remainder_size += mb[i].size;
+                       continue;
+               }
+
+               /* we have a complete line */
+               if (!priv->remainder) {
+                       consume_one(priv, mb[i].ptr, mb[i].size);
+                       continue;
+               }
+               priv->remainder = realloc(priv->remainder,
+                                         priv->remainder_size +
+                                         mb[i].size);
+               memcpy(priv->remainder + priv->remainder_size,
+                      mb[i].ptr, mb[i].size);
+               consume_one(priv, priv->remainder,
+                           priv->remainder_size + mb[i].size);
+               free(priv->remainder);
+               priv->remainder = NULL;
+               priv->remainder_size = 0;
+       }
+       if (priv->remainder) {
+               consume_one(priv, priv->remainder, priv->remainder_size);
+               free(priv->remainder);
+               priv->remainder = NULL;
+               priv->remainder_size = 0;
+       }
+       return 0;
+}
diff --git a/xdiff-interface.h b/xdiff-interface.h
new file mode 100644 (file)
index 0000000..1346908
--- /dev/null
@@ -0,0 +1,21 @@
+#ifndef XDIFF_INTERFACE_H
+#define XDIFF_INTERFACE_H
+
+#include "xdiff/xdiff.h"
+
+struct xdiff_emit_state;
+
+typedef void (*xdiff_emit_consume_fn)(void *, char *, unsigned long);
+
+struct xdiff_emit_state {
+       xdiff_emit_consume_fn consume;
+       char *remainder;
+       unsigned long remainder_size;
+};
+
+int xdiff_outf(void *priv_, mmbuffer_t *mb, int nbuf);
+int parse_hunk_header(char *line, int len,
+                     int *ob, int *on,
+                     int *nb, int *nn);
+
+#endif