object-store.txt 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. .. _tutorial-object-store:
  2. The object store
  3. ================
  4. The objects are stored in the ``object store`` of the repository.
  5. >>> from dulwich.repo import Repo
  6. >>> repo = Repo.init("myrepo", mkdir=True)
  7. Initial commit
  8. --------------
  9. When you use Git, you generally add or modify content. As our repository is
  10. empty for now, we'll start by adding a new file::
  11. >>> from dulwich.objects import Blob
  12. >>> blob = Blob.from_string(b"My file content\n")
  13. >>> print(blob.id.decode('ascii'))
  14. c55063a4d5d37aa1af2b2dad3a70aa34dae54dc6
  15. Of course you could create a blob from an existing file using ``from_file``
  16. instead.
  17. As said in the introduction, file content is separated from file name. Let's
  18. give this content a name::
  19. >>> from dulwich.objects import Tree
  20. >>> tree = Tree()
  21. >>> tree.add(b"spam", 0o100644, blob.id)
  22. Note that "0o100644" is the octal form for a regular file with common
  23. permissions. You can hardcode them or you can use the ``stat`` module.
  24. The tree state of our repository still needs to be placed in time. That's the
  25. job of the commit::
  26. >>> from dulwich.objects import Commit, parse_timezone
  27. >>> from time import time
  28. >>> commit = Commit()
  29. >>> commit.tree = tree.id
  30. >>> author = b"Your Name <your.email@example.com>"
  31. >>> commit.author = commit.committer = author
  32. >>> commit.commit_time = commit.author_time = int(time())
  33. >>> tz = parse_timezone(b'-0200')[0]
  34. >>> commit.commit_timezone = commit.author_timezone = tz
  35. >>> commit.encoding = b"UTF-8"
  36. >>> commit.message = b"Initial commit"
  37. Note that the initial commit has no parents.
  38. At this point, the repository is still empty because all operations happen in
  39. memory. Let's "commit" it.
  40. >>> object_store = repo.object_store
  41. >>> object_store.add_object(blob)
  42. Now the ".git/objects" folder contains a first SHA-1 file. Let's continue
  43. saving the changes::
  44. >>> object_store.add_object(tree)
  45. >>> object_store.add_object(commit)
  46. Now the physical repository contains three objects but still has no branch.
  47. Let's create the master branch like Git would::
  48. >>> repo.refs[b'refs/heads/master'] = commit.id
  49. The master branch now has a commit where to start. When we commit to master, we
  50. are also moving HEAD, which is Git's currently checked out branch:
  51. >>> head = repo.refs[b'HEAD']
  52. >>> head == commit.id
  53. True
  54. >>> head == repo.refs[b'refs/heads/master']
  55. True
  56. How did that work? As it turns out, HEAD is a special kind of ref called a
  57. symbolic ref, and it points at master. Most functions on the refs container
  58. work transparently with symbolic refs, but we can also take a peek inside HEAD:
  59. >>> import sys
  60. >>> print(repo.refs.read_ref(b'HEAD').decode(sys.getfilesystemencoding()))
  61. ref: refs/heads/master
  62. Normally, you won't need to use read_ref. If you want to change what ref HEAD
  63. points to, in order to check out another branch, just use set_symbolic_ref.
  64. Now our repository is officially tracking a branch named "master" referring to a
  65. single commit.
  66. Playing again with Git
  67. ----------------------
  68. At this point you can come back to the shell, go into the "myrepo" folder and
  69. type ``git status`` to let Git confirm that this is a regular repository on
  70. branch "master".
  71. Git will tell you that the file "spam" is deleted, which is normal because
  72. Git is comparing the repository state with the current working copy. And we
  73. have absolutely no working copy using Dulwich because we don't need it at
  74. all!
  75. You can checkout the last state using ``git checkout -f``. The force flag
  76. will prevent Git from complaining that there are uncommitted changes in the
  77. working copy.
  78. The file ``spam`` appears and with no surprise contains the same bytes as the
  79. blob::
  80. $ cat spam
  81. My file content
  82. Changing a File and Committing it
  83. ---------------------------------
  84. Now we have a first commit, the next one will show a difference.
  85. As seen in the introduction, it's about making a path in a tree point to a
  86. new blob. The old blob will remain to compute the diff. The tree is altered
  87. and the new commit'task is to point to this new version.
  88. Let's first build the blob::
  89. >>> from dulwich.objects import Blob
  90. >>> spam = Blob.from_string(b"My new file content\n")
  91. >>> print(spam.id.decode('ascii'))
  92. 16ee2682887a962f854ebd25a61db16ef4efe49f
  93. An alternative is to alter the previously constructed blob object::
  94. >>> blob.data = b"My new file content\n"
  95. >>> print(blob.id.decode('ascii'))
  96. 16ee2682887a962f854ebd25a61db16ef4efe49f
  97. In any case, update the blob id known as "spam". You also have the
  98. opportunity of changing its mode::
  99. >>> tree[b"spam"] = (0o100644, spam.id)
  100. Now let's record the change::
  101. >>> from dulwich.objects import Commit
  102. >>> from time import time
  103. >>> c2 = Commit()
  104. >>> c2.tree = tree.id
  105. >>> c2.parents = [commit.id]
  106. >>> c2.author = c2.committer = b"John Doe <john@example.com>"
  107. >>> c2.commit_time = c2.author_time = int(time())
  108. >>> c2.commit_timezone = c2.author_timezone = 0
  109. >>> c2.encoding = b"UTF-8"
  110. >>> c2.message = b'Changing "spam"'
  111. In this new commit we record the changed tree id, and most important, the
  112. previous commit as the parent. Parents are actually a list because a commit
  113. may happen to have several parents after merging branches.
  114. Let's put the objects in the object store::
  115. >>> repo.object_store.add_object(spam)
  116. >>> repo.object_store.add_object(tree)
  117. >>> repo.object_store.add_object(c2)
  118. You can already ask git to introspect this commit using ``git show`` and the
  119. value of ``c2.id`` as an argument. You'll see the difference will the
  120. previous blob recorded as "spam".
  121. The diff between the previous head and the new one can be printed using
  122. write_tree_diff::
  123. >>> from dulwich.patch import write_tree_diff
  124. >>> from io import BytesIO
  125. >>> out = BytesIO()
  126. >>> write_tree_diff(out, repo.object_store, commit.tree, tree.id)
  127. >>> import sys; _ = sys.stdout.write(out.getvalue().decode('ascii'))
  128. diff --git a/spam b/spam
  129. index c55063a..16ee268 100644
  130. --- a/spam
  131. +++ b/spam
  132. @@ -1 +1 @@
  133. -My file content
  134. +My new file content
  135. You won't see it using git log because the head is still the previous
  136. commit. It's easy to remedy::
  137. >>> repo.refs[b'refs/heads/master'] = c2.id
  138. Now all git tools will work as expected.