此處顯示的所有代碼都源自 以確保正確性。了解這一點還可以讓您更容易地為自己的測試目的運行代碼,您所需要的只是一個git python的開發人員安裝。


第一步是創建 git.Repo 對象來表示存儲庫。

from git import Repo

# rorepo is a Repo instance pointing to the git-python repository.
# For all you know, the first argument to Repo is a path to the repository
# you want to work with
repo = Repo(self.rorepo.working_tree_dir)
assert not repo.bare

在上面的示例中,目錄 self.rorepo.working_tree_dir 等于 /Users/mtrier/Development/git-python 是我的工作存儲庫,其中包含 .git 目錄。還可以使用 bare 儲存庫。

bare_repo = Repo.init(os.path.join(rw_dir, 'bare-repo'), bare=True)
assert bare_repo.bare


repo.config_reader()             # get a config reader for read-only access
with repo.config_writer():       # get a config writer to change configuration
    pass                         # call release() to be sure changes are written and locks are released


assert not bare_repo.is_dirty()  # check the dirty state
repo.untracked_files             # retrieve a list of untracked files
# ['my_untracked_file']


cloned_repo = repo.clone(os.path.join(rw_dir, 'to/this/path'))
assert cloned_repo.__class__ is Repo     # clone an existing repository
assert Repo.init(os.path.join(rw_dir, 'path/for/new/repo')).__class__ is Repo


with open(os.path.join(rw_dir, 'repo.tar'), 'wb') as fp:




assert os.path.isdir(cloned_repo.working_tree_dir)                   # directory with your work files
assert cloned_repo.git_dir.startswith(cloned_repo.working_tree_dir)  # directory containing the git repository
assert bare_repo.working_tree_dir is None                            # bare repositories have no working tree

Heads 頭在吉特語中是樹枝。 References 是指向特定提交或其他引用的指針。頭部和 Tags 是一種參考。gitpython允許您非常直觀地查詢它們。

self.assertEqual(repo.head.ref, repo.heads.master,  # head is a sym-ref pointing to master
                 "It's ok if TC not running from `master`.")
self.assertEqual(repo.tags['0.3.5'], repo.tag('refs/tags/0.3.5'))   # you can access tags in various ways too
self.assertEqual(repo.refs.master, repo.heads['master'])            # .refs provides all refs, ie heads ...

if 'TRAVIS' not in os.environ:
    self.assertEqual(repo.refs['origin/master'], repo.remotes.origin.refs.master)  # ... remotes ...
self.assertEqual(repo.refs['0.3.5'], repo.tags['0.3.5'])             # ... and tags


new_branch = cloned_repo.create_head('feature')               # create a new branch ...
assert cloned_repo.active_branch != new_branch                # which wasn't checked out yet ...
self.assertEqual(new_branch.commit, cloned_repo.active_branch.commit)  # pointing to the checked-out commit
# It's easy to let a branch point to the previous commit, without affecting anything else
# Each reference provides access to the git object it points to, usually commits
assert new_branch.set_commit('HEAD~1').commit == cloned_repo.active_branch.commit.parents[0]


past = cloned_repo.create_tag('past', ref=new_branch,
                              message="This is a tag-object pointing to %s" %
self.assertEqual(past.commit, new_branch.commit)        # the tag points to the specified commit
assert past.tag.message.startswith("This is")  # and its object carries the message provided

now = cloned_repo.create_tag('now')            # This is a tag-reference. It may not carry meta-data
assert now.tag is None

你可以向下移動到 git objects 通過引用和其他對象。一些物體 commits 有其他要查詢的元數據。

assert now.commit.message != past.commit.message
# You can read objects directly through binary streams, no working tree required
assert (now.commit.tree / 'VERSION')'ascii').startswith('3')

# You can traverse trees as well to handle all contained files of a particular commit
file_count = 0
tree_count = 0
tree = past.commit.tree
for item in tree.traverse():
    file_count += item.type == 'blob'
    tree_count += item.type == 'tree'
assert file_count and tree_count                        # we have accumulated all directories and files
self.assertEqual(len(tree.blobs) + len(tree.trees), len(tree))   # a tree is iterable on its children

Remotes 允許處理fetch、pull和push操作,同時向 progress delegates .

from git import RemoteProgress

class MyProgressPrinter(RemoteProgress):
    def update(self, op_code, cur_count, max_count=None, message=''):
        print(op_code, cur_count, max_count, cur_count / (max_count or 100.0), message or "NO MESSAGE")
# end

self.assertEqual(len(cloned_repo.remotes), 1)                    # we have been cloned, so should be one remote
self.assertEqual(len(bare_repo.remotes), 0)                      # this one was just initialized
origin = bare_repo.create_remote('origin', url=cloned_repo.working_tree_dir)
assert origin.exists()
for fetch_info in origin.fetch(progress=MyProgressPrinter()):
    print("Updated %s to %s" % (fetch_info.ref, fetch_info.commit))
# create a local branch at the latest fetched master. We specify the name statically, but you have all
# information to do it programatically as well.
bare_master = bare_repo.create_head('master', origin.refs.master)
assert not bare_repo.delete_remote(origin).exists()
# push and pull behave very similarly

這個 index 也被稱為吉特演講中的舞臺。它用于準備新的提交,并可用于保存合并操作的結果。我們的索引實現允許數據流到索引中,這對于沒有工作樹的裸存儲庫很有用。

self.assertEqual(new_branch.checkout(), cloned_repo.active_branch)     # checking out branch adjusts the wtree
self.assertEqual(new_branch.commit, past.commit)                       # Now the past is checked out

new_file_path = os.path.join(cloned_repo.working_tree_dir, 'my-new-file')
open(new_file_path, 'wb').close()                             # create new file in working tree
cloned_repo.index.add([new_file_path])                        # add it to the index
# Commit the changes to deviate masters history
cloned_repo.index.commit("Added a new file in the past - for later merege")

# prepare a merge
master = cloned_repo.heads.master                         # right-hand side is ahead of us, in the future
merge_base = cloned_repo.merge_base(new_branch, master)   # allows for a three-way merge
cloned_repo.index.merge_tree(master, base=merge_base)     # write the merge result into index
cloned_repo.index.commit("Merged past and now into future ;)",
                         parent_commits=(new_branch.commit, master.commit))

# now new_branch is ahead of master, which probably should be checked out and reset softly.
# note that all these operations didn't touch the working tree, as we managed it ourselves.
# This definitely requires you to know what you are doing :) !
assert os.path.basename(new_file_path) in new_branch.commit.tree  # new file is now in tree
master.commit = new_branch.commit            # let master point to most recent commit
cloned_repo.head.reference = master          # we adjusted just the reference, not the working tree or index

Submodules 表示Git子模塊的所有方面,這允許您查詢其所有相關信息,并以各種方式進行操作。

# create a new submodule and check it out on the spot, setup to track master branch of `bare_repo`
# As our GitPython repository has submodules already that point to GitHub, make sure we don't
# interact with them
for sm in cloned_repo.submodules:
    assert not sm.remove().exists()                   # after removal, the sm doesn't exist anymore
sm = cloned_repo.create_submodule('mysubrepo', 'path/to/subrepo', url=bare_repo.git_dir, branch='master')

# .gitmodules was written and added to the index, which is now being committed
cloned_repo.index.commit("Added submodule")
assert sm.exists() and sm.module_exists()             # this submodule is defintely available
sm.remove(module=True, configuration=False)           # remove the working tree
assert sm.exists() and not sm.module_exists()         # the submodule itself is still available

# update all submodules, non-recursively to save time, this method is very powerful, go have a look
assert sm.module_exists()                             # The submodules working tree was checked out by update


References 是提交圖的提示,從中可以輕松地檢查項目的歷史記錄。

import git
repo = git.Repo.clone_from(self._small_repo_url(), os.path.join(rw_dir, 'repo'), branch='master')

heads = repo.heads
master = heads.master       # lists can be accessed by name for convenience
master.commit               # the commit pointed to by head called master
master.rename('new_name')   # rename heads

Tags 是(通常不可變)對提交和/或標記對象的引用。

tags = repo.tags
tagref = tags[0]
tagref.tag                  # tags may have tag objects carrying additional information
tagref.commit               # but they always point to commits
repo.delete_tag(tagref)     # delete or
repo.create_tag("my_tag")   # create tags using the repo for convenience

A symbolic reference 是引用的特殊情況,因為它指向另一個引用而不是提交。

head = repo.head            # the head points to the active branch/ref
master = head.reference     # retrieve the reference the head points to
master.commit               # from here you use it as any other reference

訪問 reflog 很容易。

log = master.log()
log[0]                      # first (i.e. oldest) reflog entry
log[-1]                     # last (i.e. most recent) reflog entry


您可以輕松創建和刪除 reference types 或者修改它們指向的位置。

new_branch = repo.create_head('new')     # create a new one
new_branch.commit = 'HEAD~10'            # set branch to another commit without changing index or working trees
repo.delete_head(new_branch)             # delete an existing head - only works if it is not checked out

創建或刪除 tags 同樣的方法,除非你以后不能改變它們。

new_tag = repo.create_tag('my_new_tag', message='my message')
# You cannot change the commit a tag points to. Tags need to be re-created
self.assertRaises(AttributeError, setattr, new_tag, 'commit', repo.commit('HEAD~1'))

改變 symbolic reference 以較低的成本切換分支(無需調整索引或工作樹)。

new_branch = repo.create_head('another-branch')
repo.head.reference = new_branch



Git只知道4種不同的對象類型 Blobs , Trees , CommitsTags .


hc = repo.head.commit
hct = hc.tree
hc != hct                           # @NoEffect
hc != repo.tags[0]                  # @NoEffect
hc == repo.head.reference.commit    # @NoEffect


self.assertEqual(hct.type, 'tree')           # preset string type, being a class attribute
assert hct.size > 0                 # size in bytes
assert len(hct.hexsha) == 40
assert len(hct.binsha) == 20

Index objects 是可以放入Git索引的對象。這些對象是樹、塊和子模塊,它們還知道文件系統中的路徑以及它們的模式。

self.assertEqual(hct.path, '')                  # root tree has no path
assert hct.trees[0].path != ''         # the first contained item has one though
self.assertEqual(hct.mode, 0o40000)              # trees have the mode of a linux directory
self.assertEqual(hct.blobs[0].mode, 0o100644)   # blobs have specific mode, comparable to a standard linux fs

通路 blob 使用流的數據(或任何對象數據)。

hct.blobs[0]        # stream object to read data from
hct.blobs[0].stream_data(open(os.path.join(rw_dir, 'blob_data'), 'wb'))  # write data to given stream


Commit 對象包含有關特定提交的信息。使用中的引用獲取提交 Examining References 或者如下。




fifty_first_commits = list(repo.iter_commits('master', max_count=50))
assert len(fifty_first_commits) == 50
# this will return commits 21-30 from the commit list as traversed backwards master
ten_commits_past_twenty = list(repo.iter_commits('master', max_count=10, skip=20))
assert len(ten_commits_past_twenty) == 10
assert fifty_first_commits[20:30] == ten_commits_past_twenty


headcommit = repo.head.commit
assert len(headcommit.hexsha) == 40
assert len(headcommit.parents) > 0
assert headcommit.tree.type == 'tree'
assert len( != 0
assert isinstance(headcommit.authored_date, int)
assert len( != 0
assert isinstance(headcommit.committed_date, int)
assert headcommit.message != ''

注:日期時間用 seconds since epoch 格式。轉換為人類可讀形式可以用 time module 方法。

import time
time.strftime("%a, %d %b %Y %H:%M", time.gmtime(headcommit.committed_date))

您可以通過將調用鏈接到 parents

assert headcommit.parents[0].parents[0].parents[0] == repo.commit('master^^^')

上面對應 master^^^master~3 用吉特的話說。


A tree 記錄指向目錄內容的指針。假設您想要主分支上最新提交的根目錄樹

tree = repo.heads.master.commit.tree
assert len(tree.hexsha) == 40


assert len(tree.trees) > 0          # trees are subdirectories
assert len(tree.blobs) > 0          # blobs are files
assert len(tree.blobs) + len(tree.trees) == len(tree)


self.assertEqual(tree['smmap'], tree / 'smmap')          # access by index and by sub-path
for entry in tree:                                         # intuitive iteration of tree members
blob = tree.trees[1].blobs[0]                              # let's get a blob in a sub-tree
assert len(blob.path) < len(blob.abspath)
self.assertEqual(tree.trees[1].name + '/' +, blob.path)   # this is how relative blob path generated
self.assertEqual(tree[blob.path], blob)                             # you can use paths like 'dir/file' in tree


assert tree / 'smmap' == tree['smmap']
assert tree / blob.path == tree[blob.path]


# This example shows the various types of allowed ref-specs
assert repo.tree() == repo.head.commit.tree
past = repo.commit('HEAD~5')
assert repo.tree(past) == repo.tree(past.hexsha)
self.assertEqual(repo.tree('v0.8.1').type, 'tree')        # yes, you can provide any refspec - works everywhere


assert len(tree) < len(list(tree.traverse()))


如果樹返回子模塊對象,它們將假定它們存在于當前頭部的提交中。它源于的樹可能是在另一個提交中扎根的,但它不知道。這就是為什么調用者必須使用 set_parent_commit(my_commit) 方法。


Git索引是包含要在下一次提交時寫入的更改或最終必須在其中進行合并的階段。您可以使用 IndexFile 對象。輕松修改索引

index = repo.index
# The index contains all blobs in a flat list
assert len(list(index.iter_blobs())) == len([o for o in repo.head.commit.tree.traverse() if o.type == 'blob'])
# Access blob objects
for (_path, _stage), entry in index.entries.items():
new_file_path = os.path.join(repo.working_tree_dir, 'new-file-name')
open(new_file_path, 'w').close()
index.add([new_file_path])                                             # add a new file to the index
index.remove(['LICENSE'])                                              # remove an existing one
assert os.path.isfile(os.path.join(repo.working_tree_dir, 'LICENSE'))  # working tree is untouched

self.assertEqual(index.commit("my commit message").type, 'commit')              # commit changed index
repo.active_branch.commit = repo.commit('HEAD~1')                      # forget last commit

from git import Actor
author = Actor("An author", "")
committer = Actor("A committer", "")
# commit by commit message and author and committer
index.commit("my commit message", author=author, committer=committer)


from git import IndexFile
# loads a tree into a temporary index, which exists just in memory
IndexFile.from_tree(repo, 'HEAD~1')
# merge two trees three-way into memory
merge_index = IndexFile.from_tree(repo, 'HEAD~10', 'HEAD', repo.merge_base('HEAD~10', 'HEAD'))
# and persist it
merge_index.write(os.path.join(rw_dir, 'merged_index'))


Remotes 用作外部存儲庫的別名,以便于推送和提取它們

empty_repo = git.Repo.init(os.path.join(rw_dir, 'empty'))
origin = empty_repo.create_remote('origin', repo.remotes.origin.url)
assert origin.exists()
assert origin == empty_repo.remotes.origin == empty_repo.remotes['origin']
origin.fetch()                  # assure we actually have data. fetch() returns useful information
# Setup a local tracking branch of a remote branch
empty_repo.create_head('master', origin.refs.master)  # create local branch "master" from remote "master"
empty_repo.heads.master.set_tracking_branch(origin.refs.master)  # set local "master" to track remote "master
empty_repo.heads.master.checkout()  # checkout local "master" to working tree
# Three above commands in one:
empty_repo.create_head('master', origin.refs.master).set_tracking_branch(origin.refs.master).checkout()
# rename remotes
# push and pull behaves similarly to `git push|pull`
# assert not empty_repo.delete_remote(origin).exists()     # create and delete remotes


assert origin.url == repo.remotes.origin.url
with origin.config_writer as cw:
    cw.set("pushurl", "other_url")

# Please note that in python 2, writing origin.config_writer.set(...) is totally safe.
# In py3 __del__ calls can be delayed, thus not writing changes in time.

還可以使用git命令上的新上下文管理器(例如,用于使用特定的ssh密鑰)指定每次調用的自定義環境。以下示例適用于 git 開始于 v2.3 ::

ssh_cmd = 'ssh -i id_deployment_key'
with repo.git.custom_environment(GIT_SSH_COMMAND=ssh_cmd):

這個腳本設置了一個自定義腳本來代替 ssh ,可用于 git 之前 v2.3 ::

ssh_executable = os.path.join(rw_dir, '')
with repo.git.custom_environment(GIT_SSH=ssh_executable):

下面是一個示例可執行文件,它可以用來代替 ssh_executable 以上:

exec /usr/bin/ssh -o StrictHostKeyChecking=no -i $ID_RSA "$@"

請注意,腳本必須是可執行的(即 chomd +x script.shStrictHostKeyChecking=no 用于避免提示將主機密鑰保存到 ~/.ssh/known_hosts ,如果您將其作為守護進程運行,則會發生這種情況。

你也可以看看 Git.update_environment(...) 如果您希望更永久地設置更改的環境。


Submodules 使用gitpython提供的方法可以方便地處理,而且作為一個額外的好處,gitpython提供的功能比其原始的C-Git實現更智能、更不易出錯,也就是說,在更新子模塊時,gitpython會努力保持存儲庫的一致性。安全或調整現有配置。

repo = self.rorepo
sms = repo.submodules

assert len(sms) == 1
sm = sms[0]
self.assertEqual(, 'gitdb')                         # git-python has gitdb as single submodule ...
self.assertEqual(sm.children()[0].name, 'smmap')           # ... which has smmap as single submodule

# The module is the repository referenced by the submodule
assert sm.module_exists()                         # the module is available, which doesn't have to be the case.
assert sm.module().working_tree_dir.endswith('gitdb')
# the submodule's absolute path is the module's path
assert sm.abspath == sm.module().working_tree_dir
self.assertEqual(len(sm.hexsha), 40)                       # Its sha defines the commit to checkout
assert sm.exists()                                # yes, this submodule is valid and exists
# read its configuration conveniently
assert sm.config_reader().get_value('path') == sm.path
self.assertEqual(len(sm.children()), 1)                    # query the submodule hierarchy

除了查詢功能外,您還可以將子模塊的存儲庫移動到其他路徑<move(…)>,編寫其配置<config _writer()。設置value(…).release()`>,更新其工作樹<``update(…)>,然后刪除或添加它們<remove(…)`, ``add(...) >

如果您通過遍歷一個樹對象(該樹對象不是在頭的commit中建立的)獲得子模塊對象,則必須通知子模塊其實際commit,以便使用 set_parent_commit(...) 方法。

特殊 RootModule 類型允許您將主存儲庫視為子模塊層次結構的根,這允許非常方便的子模塊處理。其 update(...) 方法被重新實現,以便在子模塊隨時間改變其值時提供更新子模塊的高級方法。更新方法將跟蹤更改,并確保您的工作樹和子模塊簽出保持一致,這對于刪除或添加子模塊以僅命名兩個已處理的案例非常有用。

此外,gitpython還添加了跟蹤特定分支的功能,而不僅僅是提交。受自定義更新方法的支持,您可以自動將子模塊更新到遠程存儲庫中可用的最新版本,并跟蹤這些子模塊的更改和移動。要使用它,請將要跟蹤的分支的名稱設置為 submodule.$name.branch 選擇權 .git模塊 文件,并在結果存儲庫上使用gitpython更新方法 to_latest_revision 參數已打開。在后一種情況下,子模塊的sha將被忽略,而本地跟蹤分支將自動更新到相應的遠程分支,前提是沒有本地更改。產生的行為與svn::externals中的行為非常相似,后者有時很有用。


diff通??梢酝ㄟ^ Diffable 因為他們提供了 diff 方法。此操作生成 DiffIndex 允許您輕松訪問有關路徑的差異信息。


hcommit = repo.head.commit
hcommit.diff()                  # diff tree against index
hcommit.diff('HEAD~1')          # diff tree against previous tree
hcommit.diff(None)              # diff tree against working tree

index = repo.index
index.diff()                    # diff index against itself yielding empty diff
index.diff(None)                # diff index against working copy
index.diff('HEAD')              # diff index against current HEAD tree


# Traverse added Diff objects only
for diff_added in hcommit.diff('HEAD~1').iter_change_type('A'):


  • 您的頭指向的索引樹和提交樹之間的差異

  • 使用 repo.index.diff(repo.head.commit)

  • 索引和工作樹之間的差異

  • 使用 repo.index.diff(None)

  • 未跟蹤文件列表

  • 使用 repo.untracked_files


在類似的分支之間切換 git checkout ,您實際上需要將頭部符號引用指向新分支,并將索引和工作副本重置為匹配。一個簡單的手工方法是

# Reset our working tree 10 commits into the past
past_branch = repo.create_head('past_branch', 'HEAD~10')
repo.head.reference = past_branch
assert not repo.head.is_detached
# reset the index and working tree to match the pointed-to commit
repo.head.reset(index=True, working_tree=True)

# To detach your head, you have to point to a commit directly
repo.head.reference = repo.commit('HEAD~5')
assert repo.head.is_detached
# now our head points 15 commits into the past, whereas the working tree
# and index are 10 commits in the past

但是,前面的方法會殘忍地覆蓋用戶在工作副本和索引中所做的更改,并且比 git-checkout . 后者通常會阻止你破壞你的工作。使用以下更安全的方法。

# checkout the branch using git-checkout. It will fail as the working tree appears dirty
self.assertRaises(git.GitCommandError, repo.heads.master.checkout)



import git

repo_dir = os.path.join(rw_dir, 'my-new-repo')
file_name = os.path.join(repo_dir, 'new-file')

r = git.Repo.init(repo_dir)
# This function just creates an empty file ...
open(file_name, 'wb').close()
r.index.commit("initial commit")



如果由于未包裝而缺少功能,您可以方便地使用 git 直接命令。它由每個存儲庫實例擁有。

git = repo.git
git.checkout('HEAD', b="my_new_branch")         # create a new branch
git.branch('-D', 'another-new-one')             # pass strings for full control over argument order
git.for_each_ref()                              # '-' becomes '_' when calling it


關鍵字參數轉換為命令行上的短關鍵字參數和長關鍵字參數。特殊的概念 git.command(flag=True) 將創建一個沒有值的標志,如 command --flag .

如果 None 在參數中找到,將自動刪除。作為參數傳遞的列表和元組將遞歸地解包到各個參數。對象被轉換為字符串 str(...) 功能。


git.Repo 實例由其對象數據庫實例提供支持,該實例將在提取任何數據或寫入新對象時使用。



gitdb是git對象數據庫的純Python實現。它是gitpython 0.3中使用的默認數據庫。它在處理大型文件時使用較少的內存,但在從密集的存儲庫中提取大量小對象時將慢2到5倍:

repo = Repo("path/to/repo", odbt=GitDB)


git命令數據庫使用持久的git cat文件實例來讀取存儲庫信息。它們在所有條件下都運行得非???,但會為進程本身消耗額外的內存。在提取大型文件時,內存使用率將遠遠高于 GitDB ::

repo = Repo("path/to/repo", odbt=GitCmdObjectDB)




  • 如果設置為非0,所有執行的git命令將在發生時顯示。

  • 如果設置為 full ,執行的git命令 _and_ 它在stdout和stderr上的整個輸出將在它們發生時顯示出來。

NOTE :所有日志記錄都使用python記錄器輸出,因此請確保您的程序配置為顯示信息級消息。如果不是這樣,請嘗試將以下內容添加到程序中:

import logging

  • 如果設置了,它應該包含指向g it可執行文件的完整路徑,例如 c:\Program Files (x86)\Git\bin\git.exe 在Windows或 /usr/bin/git 在Linux上。