diff -Nru dulwich-0.4.0/bin/dulwich dulwich-0.4.1/bin/dulwich --- dulwich-0.4.0/bin/dulwich 2010-01-18 08:01:00.000000000 +0000 +++ dulwich-0.4.1/bin/dulwich 2010-01-18 08:01:00.000000000 +0000 @@ -31,156 +31,164 @@ def cmd_fetch_pack(args): - from dulwich.repo import Repo - opts, args = getopt(args, "", ["all"]) - opts = dict(opts) - client, path = get_transport_and_path(args.pop(0)) - if "--all" in opts: - determine_wants = r.object_store.determine_wants_all - else: - determine_wants = lambda x: [y for y in args if not y in r.object_store] - r = Repo(".") - graphwalker = r.get_graph_walker() - f, commit = r.object_store.add_pack() - try: - client.fetch_pack(path, determine_wants, graphwalker, f.write, sys.stdout.write) - finally: - commit() + from dulwich.repo import Repo + opts, args = getopt(args, "", ["all"]) + opts = dict(opts) + client, path = get_transport_and_path(args.pop(0)) + r = Repo(".") + if "--all" in opts: + determine_wants = r.object_store.determine_wants_all + else: + determine_wants = lambda x: [y for y in args if not y in r.object_store] + graphwalker = r.get_graph_walker() + client.fetch(path, r.object_store, determine_wants) def cmd_log(args): - from dulwich.repo import Repo - opts, args = getopt(args, "", []) - r = Repo(".") - todo = [r.head()] - done = set() - while todo: - sha = todo.pop() - assert isinstance(sha, str) - if sha in done: - continue - done.add(sha) - commit = r.commit(sha) - print "-" * 50 - print "commit: %s" % sha - if len(commit.parents) > 1: - print "merge: %s" % "...".join(commit.parents[1:]) - print "author: %s" % commit.author - print "committer: %s" % commit.committer - print "" - print commit.message - print "" - todo.extend([p for p in commit.parents if p not in done]) + from dulwich.repo import Repo + opts, args = getopt(args, "", []) + r = Repo(".") + todo = [r.head()] + done = set() + while todo: + sha = todo.pop() + assert isinstance(sha, str) + if sha in done: + continue + done.add(sha) + commit = r.commit(sha) + print "-" * 50 + print "commit: %s" % sha + if len(commit.parents) > 1: + print "merge: %s" % "...".join(commit.parents[1:]) + print "author: %s" % commit.author + print "committer: %s" % commit.committer + print "" + print commit.message + print "" + todo.extend([p for p in commit.parents if p not in done]) def cmd_dump_pack(args): - from dulwich.errors import ApplyDeltaError - from dulwich.pack import Pack, sha_to_hex - import os - import sys - - opts, args = getopt(args, "", []) - - if args == []: - print "Usage: dulwich dump-pack FILENAME" - sys.exit(1) - - basename, _ = os.path.splitext(args[0]) - x = Pack(basename) - print "Object names checksum: %s" % x.name() - print "Checksum: %s" % sha_to_hex(x.get_stored_checksum()) - if not x.check(): - print "CHECKSUM DOES NOT MATCH" - print "Length: %d" % len(x) - for name in x: - try: - print "\t%s" % x[name] - except KeyError, k: - print "\t%s: Unable to resolve base %s" % (name, k) - except ApplyDeltaError, e: - print "\t%s: Unable to apply delta: %r" % (name, e) + from dulwich.errors import ApplyDeltaError + from dulwich.pack import Pack, sha_to_hex + import os + import sys + + opts, args = getopt(args, "", []) + + if args == []: + print "Usage: dulwich dump-pack FILENAME" + sys.exit(1) + + basename, _ = os.path.splitext(args[0]) + x = Pack(basename) + print "Object names checksum: %s" % x.name() + print "Checksum: %s" % sha_to_hex(x.get_stored_checksum()) + if not x.check(): + print "CHECKSUM DOES NOT MATCH" + print "Length: %d" % len(x) + for name in x: + try: + print "\t%s" % x[name] + except KeyError, k: + print "\t%s: Unable to resolve base %s" % (name, k) + except ApplyDeltaError, e: + print "\t%s: Unable to apply delta: %r" % (name, e) def cmd_dump_index(args): - from dulwich.index import Index + from dulwich.index import Index - opts, args = getopt(args, "", []) + opts, args = getopt(args, "", []) - if args == []: - print "Usage: dulwich dump-index FILENAME" - sys.exit(1) + if args == []: + print "Usage: dulwich dump-index FILENAME" + sys.exit(1) - filename = args[0] - idx = Index(filename) + filename = args[0] + idx = Index(filename) - for o in idx: - print o, idx[o] + for o in idx: + print o, idx[o] def cmd_init(args): - from dulwich.repo import Repo - import os - import sys - opts, args = getopt(args, "", ["--bare"]) - opts = dict(opts) - - if args == []: - path = os.getcwd() - else: - path = args[0] - - if not os.path.exists(path): - os.mkdir(path) - - if "--bare" in opts: - Repo.init_bare(path) - else: - Repo.init(path) + from dulwich.repo import Repo + import os + opts, args = getopt(args, "", ["--bare"]) + opts = dict(opts) + + if args == []: + path = os.getcwd() + else: + path = args[0] + + if not os.path.exists(path): + os.mkdir(path) + + if "--bare" in opts: + Repo.init_bare(path) + else: + Repo.init(path) def cmd_clone(args): - from dulwich.repo import Repo - import os - import sys - opts, args = getopt(args, "", []) - opts = dict(opts) - - if args == []: - print "usage: dulwich clone host:path [PATH]" - sys.exit(1) + from dulwich.repo import Repo + import os + import sys + opts, args = getopt(args, "", []) + opts = dict(opts) + + if args == []: + print "usage: dulwich clone host:path [PATH]" + sys.exit(1) client, host_path = get_transport_and_path(args.pop(0)) - if len(args) > 0: - path = args.pop(0) - else: - path = host_path.split("/")[-1] - - if not os.path.exists(path): - os.mkdir(path) - Repo.init(path) - r = Repo(path) - graphwalker = r.get_graph_walker() - f, commit = r.object_store.add_pack() - client.fetch_pack(host_path, r.object_store.determine_wants_all, - graphwalker, f.write, sys.stdout.write) - commit() - + if len(args) > 0: + path = args.pop(0) + else: + path = host_path.split("/")[-1] + + if not os.path.exists(path): + os.mkdir(path) + Repo.init(path) + r = Repo(path) + graphwalker = r.get_graph_walker() + f, commit = r.object_store.add_pack() + client.fetch_pack(host_path, r.object_store.determine_wants_all, + graphwalker, f.write, sys.stdout.write) + commit() + + +def cmd_commit(args): + from dulwich.repo import Repo + import os + opts, args = getopt(args, "", ["message"]) + opts = dict(opts) + r = Repo(".") + committer = "%s <%s>" % (os.getenv("GIT_COMMITTER_NAME"), + os.getenv("GIT_COMMITTER_EMAIL")) + author = "%s <%s>" % (os.getenv("GIT_AUTHOR_NAME"), + os.getenv("GIT_AUTHOR_EMAIL")) + r.do_commit(committer=committer, author=author, message=opts["--message"]) commands = { - "fetch-pack": cmd_fetch_pack, - "dump-pack": cmd_dump_pack, - "dump-index": cmd_dump_index, - "init": cmd_init, - "log": cmd_log, - "clone": cmd_clone, - } + "commit": cmd_commit, + "fetch-pack": cmd_fetch_pack, + "dump-pack": cmd_dump_pack, + "dump-index": cmd_dump_index, + "init": cmd_init, + "log": cmd_log, + "clone": cmd_clone, + } if len(sys.argv) < 2: - print "Usage: %s <%s> [OPTIONS...]" % (sys.argv[0], "|".join(commands.keys())) - sys.exit(1) + print "Usage: %s <%s> [OPTIONS...]" % (sys.argv[0], "|".join(commands.keys())) + sys.exit(1) cmd = sys.argv[1] if not cmd in commands: - print "No such subcommand: %s" % cmd - sys.exit(1) + print "No such subcommand: %s" % cmd + sys.exit(1) commands[cmd](sys.argv[2:]) diff -Nru dulwich-0.4.0/debian/changelog dulwich-0.4.1/debian/changelog --- dulwich-0.4.0/debian/changelog 2010-01-18 08:01:00.000000000 +0000 +++ dulwich-0.4.1/debian/changelog 2010-01-18 08:01:00.000000000 +0000 @@ -1,3 +1,11 @@ +dulwich (0.4.1-1) unstable; urgency=low + + * New upstream release. + + Supports time-less tags. Closes: #543240 + * Rebuilt against newer version of python-central. Closes: #551893 + + -- Jelmer Vernooij Sun, 03 Jan 2010 16:40:19 +0100 + dulwich (0.4.0-1) unstable; urgency=low * New upstream release. diff -Nru dulwich-0.4.0/docs/tutorial/2-change-file.txt dulwich-0.4.1/docs/tutorial/2-change-file.txt --- dulwich-0.4.0/docs/tutorial/2-change-file.txt 2009-10-07 11:07:10.000000000 +0100 +++ dulwich-0.4.1/docs/tutorial/2-change-file.txt 2010-01-03 15:11:55.000000000 +0000 @@ -55,7 +55,7 @@ You won't see it using git log because the head is still the previous commit. It's easy to remedy:: - >>> repo.refs['refs/heads/master'] = commit.id + >>> repo.refs['refs/heads/master'] = c2.id Now all git tools will work as expected. Though don't forget that Dulwich is still open! diff -Nru dulwich-0.4.0/dulwich/client.py dulwich-0.4.1/dulwich/client.py --- dulwich-0.4.0/dulwich/client.py 2009-10-07 11:07:10.000000000 +0100 +++ dulwich-0.4.1/dulwich/client.py 2010-01-03 15:11:55.000000000 +0000 @@ -124,6 +124,25 @@ return new_refs + def fetch(self, path, target, determine_wants=None, progress=None): + """Fetch into a target repository. + + :param path: Path to fetch from + :param target: Target repository to fetch into + :param determine_wants: Optional function to determine what refs + to fetch + :param progress: Optional progress function + :return: remote refs + """ + if determine_wants is None: + determine_wants = target.object_store.determine_wants_all + f, commit = target.object_store.add_pack() + try: + return self.fetch_pack(path, determine_wants, target.graph_walker, + f.write, progress) + finally: + commit() + def fetch_pack(self, path, determine_wants, graph_walker, pack_data, progress): """Retrieve a pack from a git smart server. diff -Nru dulwich-0.4.0/dulwich/index.py dulwich-0.4.1/dulwich/index.py --- dulwich-0.4.0/dulwich/index.py 2009-10-07 11:07:10.000000000 +0100 +++ dulwich-0.4.1/dulwich/index.py 2010-01-03 15:11:55.000000000 +0000 @@ -212,9 +212,14 @@ """Return the (git object) SHA1 for the object at a path.""" return self[path][-2] + def get_mode(self, path): + """Return the POSIX file mode for the object at a path.""" + return self[path][-6] + def iterblobs(self): """Iterate over path, sha, mode tuples for use with commit_tree.""" - for path, entry in self: + for path in self: + entry = self[path] yield path, entry[-2], cleanup_mode(entry[-6]) def clear(self): @@ -234,6 +239,28 @@ for name, value in entries.iteritems(): self[name] = value + def changes_from_tree(self, object_store, tree, want_unchanged=False): + """Find the differences between the contents of this index and a tree. + + :param object_store: Object store to use for retrieving tree contents + :param tree: SHA1 of the root tree + :param want_unchanged: Whether unchanged files should be reported + :return: Iterator over tuples with (oldpath, newpath), (oldmode, newmode), (oldsha, newsha) + """ + mine = set(self._byname.keys()) + for (name, mode, sha) in object_store.iter_tree_contents(tree): + if name in mine: + if (want_unchanged or self.get_sha1(name) != sha or + self.get_mode(name) != mode): + yield ((name, name), (mode, self.get_mode(name)), (sha, self.get_sha1(name))) + mine.remove(name) + else: + # Was removed + yield ((name, None), (mode, None), (sha, None)) + # Mention added files + for name in mine: + yield ((None, name), (None, self.get_mode(name)), (None, self.get_sha1(name))) + def commit_tree(object_store, blobs): """Commit a new tree. diff -Nru dulwich-0.4.0/dulwich/__init__.py dulwich-0.4.1/dulwich/__init__.py --- dulwich-0.4.0/dulwich/__init__.py 2010-01-18 08:01:00.000000000 +0000 +++ dulwich-0.4.1/dulwich/__init__.py 2010-01-03 15:11:55.000000000 +0000 @@ -27,4 +27,4 @@ import repo import server -__version__ = (0, 4, 0) +__version__ = (0, 4, 1) diff -Nru dulwich-0.4.0/dulwich/objects.py dulwich-0.4.1/dulwich/objects.py --- dulwich-0.4.0/dulwich/objects.py 2009-10-07 11:07:10.000000000 +0100 +++ dulwich-0.4.1/dulwich/objects.py 2010-01-03 15:11:55.000000000 +0000 @@ -1,17 +1,17 @@ # objects.py -- Access to base git objects # Copyright (C) 2007 James Westby # Copyright (C) 2008-2009 Jelmer Vernooij -# +# # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; version 2 # of the License or (at your option) a later version of the License. -# +# # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. -# +# # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, @@ -89,7 +89,7 @@ class ShaFile(object): """A git SHA file.""" - + @classmethod def _parse_legacy_object(cls, map): """Parse a legacy object, creating it and setting object._text""" @@ -120,7 +120,7 @@ def as_legacy_object(self): text = self.as_raw_string() return zlib.compress("%s %d\0%s" % (self._type, len(text), text)) - + def as_raw_string(self): if self._needs_serialization: self.serialize() @@ -146,7 +146,7 @@ self._sha = None self._needs_parsing = True self._needs_serialization = False - + @classmethod def _parse_object(cls, map): """Parse a new style object , creating it and setting object._text""" @@ -164,7 +164,7 @@ raw = map[used:] object.set_raw_string(_decompress(raw)) return object - + @classmethod def _parse_file(cls, map): word = (ord(map[0]) << 8) + ord(map[1]) @@ -172,14 +172,14 @@ return cls._parse_legacy_object(map) else: return cls._parse_object(map) - + def __init__(self): """Don't call this directly""" self._sha = None - + def _parse_text(self): """For subclasses to do initialisation time parsing""" - + @classmethod def from_file(cls, filename): """Get the contents of a SHA file on disk""" @@ -191,11 +191,11 @@ return shafile finally: f.close() - + @classmethod def from_raw_string(cls, type, string): """Creates an object of the indicated type from the raw string given. - + Type is the numeric type of an object. String is the raw uncompressed contents. """ @@ -204,10 +204,10 @@ obj.type = type obj.set_raw_string(string) return obj - + def _header(self): return "%s %lu\0" % (self._type, len(self.as_raw_string())) - + def sha(self): """The SHA1 object that is the name of this object.""" if self._needs_serialization or self._sha is None: @@ -215,11 +215,11 @@ self._sha.update(self._header()) self._sha.update(self.as_raw_string()) return self._sha - + @property def id(self): return self.sha().hexdigest() - + def get_type(self): return self._num_type @@ -227,16 +227,16 @@ self._num_type = type type = property(get_type, set_type) - + def __repr__(self): return "<%s %s>" % (self.__class__.__name__, self.id) def __ne__(self, other): return self.id != other.id - + def __eq__(self, other): """Return true id the sha of the two objects match. - + The __le__ etc methods aren't overriden as they make no sense, certainly at this level. """ @@ -257,7 +257,7 @@ def set_data(self, data): self._text = data - data = property(get_data, set_data, + data = property(get_data, set_data, "The text contained within the blob object.") @classmethod @@ -306,7 +306,10 @@ f.write("%s %s\n" % (TYPE_ID, num_type_map[self._object_type]._type)) f.write("%s %s\n" % (TAG_ID, self._name)) if self._tagger: - f.write("%s %s %d %s\n" % (TAGGER_ID, self._tagger, self._tag_time, format_timezone(self._tag_timezone))) + if self._tag_time is None: + f.write("%s %s\n" % (TAGGER_ID, self._tagger)) + else: + f.write("%s %s %d %s\n" % (TAGGER_ID, self._tagger, self._tag_time, format_timezone(self._tag_timezone))) f.write("\n") # To close headers f.write(self._message) self._text = f.getvalue() @@ -328,14 +331,20 @@ elif field == TAG_ID: self._name = value elif field == TAGGER_ID: - sep = value.index("> ") - self._tagger = value[0:sep+1] - (timetext, timezonetext) = value[sep+2:].rsplit(" ", 1) try: - self._tag_time = int(timetext) - except ValueError: #Not a unix timestamp - self._tag_time = time.strptime(timetext) - self._tag_timezone = parse_timezone(timezonetext) + sep = value.index("> ") + except ValueError: + self._tagger = value + self._tag_time = None + self._tag_timezone = None + else: + self._tagger = value[0:sep+1] + (timetext, timezonetext) = value[sep+2:].rsplit(" ", 1) + try: + self._tag_time = int(timetext) + except ValueError: #Not a unix timestamp + self._tag_time = time.strptime(timetext) + self._tag_timezone = parse_timezone(timezonetext) else: raise AssertionError("Unknown field %s" % field) self._message = f.read() @@ -354,11 +363,11 @@ object = property(get_object, set_object) name = serializable_property("name", "The name of this tag") - tagger = serializable_property("tagger", + tagger = serializable_property("tagger", "Returns the name of the person who created this tag") - tag_time = serializable_property("tag_time", + tag_time = serializable_property("tag_time", "The creation timestamp of the tag. As the number of seconds since the epoch") - tag_timezone = serializable_property("tag_timezone", + tag_timezone = serializable_property("tag_timezone", "The timezone that tag_time is in.") message = serializable_property("message", "The message attached to this tag") @@ -366,27 +375,20 @@ def parse_tree(text): ret = {} count = 0 - while count < len(text): - mode = 0 - chr = text[count] - while chr != ' ': - assert chr >= '0' and chr <= '7', "%s is not a valid mode char" % chr - mode = (mode << 3) + (ord(chr) - ord('0')) - count += 1 - chr = text[count] - count += 1 - chr = text[count] - name = '' - while chr != '\0': - name += chr - count += 1 - chr = text[count] - count += 1 - chr = text[count] - sha = text[count:count+20] - hexsha = sha_to_hex(sha) - ret[name] = (mode, hexsha) - count = count + 20 + l = len(text) + while count < l: + mode_end = text.index(' ', count) + mode = int(text[count:mode_end], 8) + + name_end = text.index('\0', mode_end) + name = text[mode_end+1:name_end] + + count = name_end+21 + + sha = text[name_end+1:count] + + ret[name] = (mode, sha_to_hex(sha)) + return ret @@ -576,10 +578,10 @@ parents = property(get_parents, set_parents) - author = serializable_property("author", + author = serializable_property("author", "The name of the author of the commit") - committer = serializable_property("committer", + committer = serializable_property("committer", "The name of the committer of the commit") message = serializable_property("message", @@ -591,10 +593,10 @@ commit_timezone = serializable_property("commit_timezone", "The zone the commit time is in") - author_time = serializable_property("author_time", + author_time = serializable_property("author_time", "The timestamp the commit was written. as the number of seconds since the epoch.") - author_timezone = serializable_property("author_timezone", + author_timezone = serializable_property("author_timezone", "Returns the zone the author time is in.") encoding = serializable_property("encoding", diff -Nru dulwich-0.4.0/dulwich/object_store.py dulwich-0.4.1/dulwich/object_store.py --- dulwich-0.4.0/dulwich/object_store.py 2009-10-07 11:07:10.000000000 +0100 +++ dulwich-0.4.1/dulwich/object_store.py 2010-01-03 15:11:55.000000000 +0000 @@ -1,16 +1,16 @@ -# object_store.py -- Object store for git objects +# object_store.py -- Object store for git objects # Copyright (C) 2008-2009 Jelmer Vernooij -# +# # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # or (at your option) a later version of the License. -# +# # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. -# +# # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, @@ -40,8 +40,8 @@ ) from dulwich.pack import ( Pack, - PackData, - iter_sha1, + PackData, + iter_sha1, load_pack_index, write_pack, write_pack_data, @@ -71,7 +71,7 @@ def get_raw(self, name): """Obtain the raw text for an object. - + :param name: sha for the object. :return: tuple with object type and object contents. """ @@ -99,6 +99,84 @@ """ raise NotImplementedError(self.add_objects) + def tree_changes(self, source, target, want_unchanged=False): + """Find the differences between the contents of two trees + + :param object_store: Object store to use for retrieving tree contents + :param tree: SHA1 of the root tree + :param want_unchanged: Whether unchanged files should be reported + :return: Iterator over tuples with (oldpath, newpath), (oldmode, newmode), (oldsha, newsha) + """ + todo = set([(source, target, "")]) + while todo: + (sid, tid, path) = todo.pop() + if sid is not None: + stree = self[sid] + else: + stree = {} + if tid is not None: + ttree = self[tid] + else: + ttree = {} + for name, oldmode, oldhexsha in stree.iteritems(): + if path == "": + oldchildpath = name + else: + oldchildpath = "%s/%s" % (path, name) + try: + (newmode, newhexsha) = ttree[name] + newchildpath = oldchildpath + except KeyError: + newmode = None + newhexsha = None + newchildpath = None + if (want_unchanged or oldmode != newmode or + oldhexsha != newhexsha): + if stat.S_ISDIR(oldmode): + if newmode is None or stat.S_ISDIR(newmode): + todo.add((oldhexsha, newhexsha, oldchildpath)) + else: + # entry became a file + todo.add((oldhexsha, None, oldchildpath)) + yield ((None, newchildpath), (None, newmode), (None, newhexsha)) + else: + if newmode is not None and stat.S_ISDIR(newmode): + # entry became a dir + yield ((oldchildpath, None), (oldmode, None), (oldhexsha, None)) + todo.add((None, newhexsha, newchildpath)) + else: + yield ((oldchildpath, newchildpath), (oldmode, newmode), (oldhexsha, newhexsha)) + + for name, newmode, newhexsha in ttree.iteritems(): + if path == "": + childpath = name + else: + childpath = "%s/%s" % (path, name) + if not name in stree: + if not stat.S_ISDIR(newmode): + yield ((None, childpath), (None, newmode), (None, newhexsha)) + else: + todo.add((None, newhexsha, childpath)) + + def iter_tree_contents(self, tree): + """Yield (path, mode, hexsha) tuples for all non-Tree objects in a tree. + + :param tree: SHA1 of the root of the tree + """ + todo = set([(tree, "")]) + while todo: + (tid, tpath) = todo.pop() + tree = self[tid] + for name, mode, hexsha in tree.iteritems(): + if tpath == "": + path = name + else: + path = "%s/%s" % (tpath, name) + if stat.S_ISDIR(mode): + todo.add((hexsha, path)) + else: + yield path, mode, hexsha + def find_missing_objects(self, haves, wants, progress=None): """Find the missing objects required for a set of revisions. diff -Nru dulwich-0.4.0/dulwich/patch.py dulwich-0.4.1/dulwich/patch.py --- dulwich-0.4.0/dulwich/patch.py 1970-01-01 01:00:00.000000000 +0100 +++ dulwich-0.4.1/dulwich/patch.py 2010-01-03 15:11:55.000000000 +0000 @@ -0,0 +1,110 @@ +# patch.py -- For dealing wih packed-style patches. +# Copryight (C) 2009 Jelmer Vernooij +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 +# of the License or (at your option) a later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. + +"""Classes for dealing with git am-style patches. + +These patches are basically unified diffs with some extra metadata tacked +on. +""" + +import difflib +import subprocess +import time + + +def write_commit_patch(f, commit, contents, progress, version=None): + """Write a individual file patch. + + :param commit: Commit object + :param progress: Tuple with current patch number and total. + :return: tuple with filename and contents + """ + (num, total) = progress + f.write("From %s %s\n" % (commit.id, time.ctime(commit.commit_time))) + f.write("From: %s\n" % commit.author) + f.write("Date: %s\n" % time.strftime("%a, %d %b %Y %H:%M:%S %Z")) + f.write("Subject: [PATCH %d/%d] %s\n" % (num, total, commit.message)) + f.write("\n") + f.write("---\n") + try: + p = subprocess.Popen(["diffstat"], stdout=subprocess.PIPE, + stdin=subprocess.PIPE) + except OSError, e: + pass # diffstat not available? + else: + (diffstat, _) = p.communicate(contents) + f.write(diffstat) + f.write("\n") + f.write(contents) + f.write("-- \n") + if version is None: + from dulwich import __version__ as dulwich_version + f.write("Dulwich %d.%d.%d\n" % dulwich_version) + else: + f.write("%s\n" % version) + + +def get_summary(commit): + """Determine the summary line for use in a filename. + + :param commit: Commit + :return: Summary string + """ + return commit.message.splitlines()[0].replace(" ", "-") + + +def write_blob_diff(f, (old_path, old_mode, old_blob), + (new_path, new_mode, new_blob)): + """Write diff file header. + + :param f: File-like object to write to + :param (old_path, old_mode, old_blob): Previous file (None if nonexisting) + :param (new_path, new_mode, new_blob): New file (None if nonexisting) + """ + def blob_id(blob): + if blob is None: + return "0" * 7 + else: + return blob.id[:7] + def lines(blob): + if blob is not None: + return blob.data.splitlines(True) + else: + return [] + if old_path is None: + old_path = "/dev/null" + else: + old_path = "a/%s" % old_path + if new_path is None: + new_path = "/dev/null" + else: + new_path = "b/%s" % new_path + f.write("diff --git %s %s\n" % (old_path, new_path)) + if old_mode != new_mode: + if new_mode is not None: + if old_mode is not None: + f.write("old file mode %o\n" % old_mode) + f.write("new file mode %o\n" % new_mode) + else: + f.write("deleted file mode %o\n" % old_mode) + f.write("index %s..%s %o\n" % ( + blob_id(old_blob), blob_id(new_blob), new_mode)) + old_contents = lines(old_blob) + new_contents = lines(new_blob) + f.writelines(difflib.unified_diff(old_contents, new_contents, + old_path, new_path)) diff -Nru dulwich-0.4.0/dulwich/repo.py dulwich-0.4.1/dulwich/repo.py --- dulwich-0.4.0/dulwich/repo.py 2009-10-07 11:07:10.000000000 +0100 +++ dulwich-0.4.1/dulwich/repo.py 2010-01-03 15:11:55.000000000 +0000 @@ -242,6 +242,19 @@ """Check if an index is present.""" return os.path.exists(self.index_path()) + def fetch(self, target, determine_wants=None, progress=None): + """Fetch objects into another repository. + + :param target: The target repository + :param determine_wants: Optional function to determine what refs to + fetch. + :param progress: Optional progress function + """ + target.object_store.add_objects( + self.fetch_objects(determine_wants, target.get_graph_walker(), + progress)) + return self.get_refs() + def fetch_objects(self, determine_wants, graph_walker, progress): """Fetch the missing objects required for a set of revisions. @@ -396,6 +409,52 @@ del self.refs[name] raise ValueError(name) + def do_commit(self, committer, message, + author=None, commit_timestamp=None, + commit_timezone=None, author_timestamp=None, + author_timezone=None, tree=None): + """Create a new commit. + + :param committer: Committer fullname + :param message: Commit message + :param author: Author fullname (defaults to committer) + :param commit_timestamp: Commit timestamp (defaults to now) + :param commit_timezone: Commit timestamp timezone (defaults to GMT) + :param author_timestamp: Author timestamp (defaults to commit timestamp) + :param author_timezone: Author timestamp timezone + (defaults to commit timestamp timezone) + :param tree: SHA1 of the tree root to use (if not specified the current index will be committed). + :return: New commit SHA1 + """ + from dulwich.index import commit_index + import time + index = self.open_index() + c = Commit() + if tree is None: + c.tree = commit_index(self.object_store, index) + else: + c.tree = tree + c.committer = committer + if commit_timestamp is None: + commit_timestamp = time.time() + c.commit_time = int(commit_timestamp) + if commit_timezone is None: + commit_timezone = 0 + c.commit_timezone = commit_timezone + if author is None: + author = committer + c.author = author + if author_timestamp is None: + author_timestamp = commit_timestamp + c.author_time = int(author_timestamp) + if author_timezone is None: + author_timezone = commit_timezone + c.author_timezone = author_timezone + c.message = message + self.object_store.add_object(c) + self.refs["HEAD"] = c.id + return c.id + @classmethod def init(cls, path, mkdir=True): controldir = os.path.join(path, ".git") diff -Nru dulwich-0.4.0/NEWS dulwich-0.4.1/NEWS --- dulwich-0.4.0/NEWS 2010-01-18 08:01:00.000000000 +0000 +++ dulwich-0.4.1/NEWS 2010-01-03 15:11:55.000000000 +0000 @@ -1,3 +1,15 @@ +0.4.1 2010-01-03 + + FEATURES + + * Add ObjectStore.iter_tree_contents() + + * Add Index.changes_from_tree() + + * Add ObjectStore.tree_changes() + + * Add functionality for writing patches in dulwich.patch. + 0.4.0 2009-10-07 DOCUMENTATION diff -Nru dulwich-0.4.0/setup.py dulwich-0.4.1/setup.py --- dulwich-0.4.0/setup.py 2010-01-18 08:01:00.000000000 +0000 +++ dulwich-0.4.1/setup.py 2010-01-18 08:01:00.000000000 +0000 @@ -2,10 +2,13 @@ # Setup file for bzr-git # Copyright (C) 2008-2009 Jelmer Vernooij -from distutils.core import setup +try: + from setuptools import setup +except ImportError: + from distutils.core import setup from distutils.extension import Extension -dulwich_version_string = '0.4.0' +dulwich_version_string = '0.4.1' include_dirs = [] # Windows MSVC support @@ -24,8 +27,8 @@ author='Jelmer Vernooij', author_email='jelmer@samba.org', long_description=""" - Simple Pure-Python implementation of the Git file formats and - protocols. Dulwich is the place where Mr. and Mrs. Git live + Simple Pure-Python implementation of the Git file formats and + protocols. Dulwich is the place where Mr. and Mrs. Git live in one of the Monty Python sketches. """, packages=['dulwich', 'dulwich.tests'],