Let's first build the blob::
- >>> spam = Blob.from_string("My new file content")
+ >>> spam = Blob.from_string("My new file content\n")
>>> spam.id
- 'fd2a0fa3ad828c5bda4b7badcbe522dc3b12af73'
+ '16ee2682887a962f854ebd25a61db16ef4efe49f'
An alternative is to alter the previously constructed blob object::
- >>> blob.data = "My new file content"
+ >>> blob.data = "My new file content\n"
>>> blob.id
- 'fd2a0fa3ad828c5bda4b7badcbe522dc3b12af73'
+ '16ee2682887a962f854ebd25a61db16ef4efe49f'
In any case, update the blob id known as "spam". You also have the
opportunity of changing its mode::
>>> object_store.add_object(c2)
You can already ask git to introspect this commit using ``git show`` and the
-value of ``commit.it`` as an argument. You'll see the difference will the
+value of ``commit.id`` as an argument. You'll see the difference will the
previous blob recorded as "spam".
You won't see it using git log because the head is still the previous
--- /dev/null
+Adding a file
+=============
+
+If you followed well, the next lesson will be straightforward.
+
+We need a new blob::
+
+ >>> ham = Blob.from_string("Another\nmultiline\nfile\n")
+ >>> ham.id
+ 'a3b5eda0b83eb8fb6e5dce91ecafda9e97269c70'
+
+But the same tree::
+
+ >>> tree["ham"] = (0100644, spam.id)
+
+And a new commit::
+
+ >>> c3 = Commit()
+ >>> c3.tree = tree.id
+ >>> c3.parents = [commit.id]
+ >>> c3.author = c3.committer = author
+ >>> c3.commit_time = c3.author_time = int(time())
+ >>> c3.commit_timezone = c3.author_timezone = tz
+ >>> c3.encoding = "UTF-8"
+ >>> c3.message = 'Adding "ham"'
+
+Save it all::
+
+ >>> object_store.add_object(spam)
+ >>> object_store.add_object(tree)
+ >>> object_store.add_object(c3)
+
+Update the head::
+
+ >>> repo.refs['refs/heads/master'] = commit.id
+
+A call to ``git show`` will confirm the addition of "spam".
+
+Remember you can also call ``git checkout -f`` to make it appear.
+
+Well... Adding "spam" was not such a good idea... We'll remove it.
--- /dev/null
+Renaming a file
+===============
+
+Remember you learned that the file name and content are distinct. So renaming
+a file is just about associating a blob id to a new name. We won't store more
+content, and the operation will be painless.
+
+Let's transfer the blob id from the old name to the new one::
+
+ >>> tree["eggs"] = tree["spam"]
+ >>> del tree["spam"]
+
+As usual, we need a commit to store the new tree id::
+
+ >>> c5 = Commit()
+ >>> c5.tree = tree.id
+ >>> c5.parents = [commit.id]
+ >>> c5.author = c5.committer = author
+ >>> c5.commit_time = c5.author_time = int(time())
+ >>> c5.commit_timezone = c5.author_timezone = tz
+ >>> c5.encoding = "UTF-8"
+ >>> c5.message = 'Rename "spam" to "eggs"'
+
+As for a deletion, we only have a tree and a commit to save::
+
+ >>> object_store.add_object(tree)
+ >>> object_store.add_object(c5)
+
+Remains to make the head bleeding-edge::
+
+ >>> repo.refs['refs/heads/master'] = commit.id
+
+As a last exercise, see how ``git show`` illustrates it.
--- /dev/null
+#!/usr/bin/env python
+# -*- encoding: UTF-8 -*-
+
+# Import from the Standard Library
+from os import F_OK, access, mkdir
+from pprint import pprint
+from shutil import rmtree
+from subprocess import call
+from time import time
+
+# Import from dulwich
+from dulwich.repo import Repo
+from dulwich.objects import Blob, Tree, Commit, parse_timezone
+
+
+DIRNAME = "myrepo"
+AUTHOR = "Your Name <your.email@example.com>"
+TZ = parse_timezone('-200')
+ENCODING = "UTF-8"
+
+
+def make_commit(repo, tree_id, message):
+ """Build a commit object on the same pattern. Only changing values are
+ required as parameters.
+ """
+ commit = Commit()
+ try:
+ commit.parents = [repo.head()]
+ except KeyError:
+ # The initial commit has no parent
+ pass
+ commit.tree = tree_id
+ commit.message = message
+ commit.author = commit.committer = AUTHOR
+ commit.commit_time = commit.author_time = int(time())
+ commit.commit_timezone = commit.author_timezone = TZ
+ commit.encoding = ENCODING
+ return commit
+
+
+
+def make_tree(repo):
+ """Return the last known tree.
+ """
+ commit_id = repo.head()
+ commit = repo.commit(commit_id)
+ tree_id = commit.tree
+ return repo.tree(tree_id)
+
+
+
+def update_master(repo, commit_id):
+ repo.refs['refs/heads/master'] = commit_id
+
+
+
+def initial_commit(repo):
+ # Add file content
+ blob = Blob.from_string("My file content\n")
+ # Add file
+ tree = Tree()
+ tree.add(0100644, "spam", blob.id)
+ # Set commit
+ commit = make_commit(repo, tree.id, "Initial commit")
+ # Initial commit
+ object_store = repo.object_store
+ object_store.add_object(blob)
+ object_store.add_object(tree)
+ object_store.add_object(commit)
+ # Update master
+ update_master(repo, commit.id)
+ # Set the master branch as the default
+ repo.refs['HEAD'] = 'ref: refs/heads/master'
+
+
+
+def test_change(repo):
+ tree = make_tree(repo)
+ # Change a file
+ spam = Blob.from_string("My new file content\n")
+ tree.add(0100644, "spam", spam.id)
+ # Set commit
+ commit = make_commit(repo, tree.id, "Change spam")
+ # Second commit
+ object_store = repo.object_store
+ object_store.add_object(spam)
+ object_store.add_object(tree)
+ object_store.add_object(commit)
+ # Update master
+ update_master(repo, commit.id)
+
+
+
+def test_add(repo):
+ tree = make_tree(repo)
+ # Add another file
+ ham = Blob.from_string("Another\nmultiline\nfile\n")
+ tree.add(0100644, "ham", ham.id)
+ # Set commit
+ commit = make_commit(repo, tree.id, "Add ham")
+ # Second commit
+ object_store = repo.object_store
+ object_store.add_object(ham)
+ object_store.add_object(tree)
+ object_store.add_object(commit)
+ # Update master
+ update_master(repo, commit.id)
+
+
+
+def test_remove(repo):
+ tree = make_tree(repo)
+ # Remove a file
+ del tree["ham"]
+ # Set commit
+ commit = make_commit(repo, tree.id, 'Remove "ham"')
+ # Third commit
+ # No blob change, just tree operation
+ object_store = repo.object_store
+ object_store.add_object(tree)
+ object_store.add_object(commit)
+ # Update master
+ update_master(repo, commit.id)
+
+
+
+def test_rename(repo):
+ tree = make_tree(repo)
+ # Rename a file
+ tree["eggs"] = tree["spam"]
+ del tree["spam"]
+ # Set commit
+ commit = make_commit(repo, tree.id, 'Rename "spam" to "eggs"')
+ # Fourth commit
+ # No blob change, just tree operation
+ object_store = repo.object_store
+ object_store.add_object(tree)
+ object_store.add_object(commit)
+ # Update master
+ update_master(repo, commit.id)
+
+
+
+def test_history(repo):
+ pprint(repo.revision_history(repo.head()))
+
+
+
+def test_file(repo):
+ tree = make_tree(repo)
+ print "entries", tree.entries()
+ mode, blob_id = tree["eggs"]
+ blob = repo.get_blob(blob_id)
+ print "eggs", repr(blob.data)
+
+
+
+if __name__ == '__main__':
+ # Creating the repository
+ if access(DIRNAME, F_OK):
+ rmtree(DIRNAME)
+ mkdir(DIRNAME)
+ repo = Repo.init(DIRNAME)
+ initial_commit(repo)
+ test_change(repo)
+ test_add(repo)
+ test_remove(repo)
+ test_rename(repo)
+ last_commit_id = repo.head()
+ call(['git', 'gc'], cwd=DIRNAME)
+ # Re-load the repo
+ del repo
+ repo = Repo(DIRNAME)
+ # XXX the ref was removed and dulwich doesn't know where to read it
+ update_master(repo, last_commit_id)
+ assert last_commit_id == repo.head()
+ test_history(repo)
+ test_file(repo)