Imported Upstream version 0.1.4.1
SVN-Git Migration
8 years ago
Binary diff not shown
0 | Michael Trier <mtrier _at_ gmail.com> | |
1 | Alan Briolat | |
2 | Florian Apolloner <florian _at_ apolloner.eu> | |
3 | David Aguilar <davvid _at_ gmail.com> |
0 | ======= | |
1 | CHANGES | |
2 | ======= | |
3 | ||
4 | 0.1.5 | |
5 | ===== | |
6 | ||
7 | * removed ``method_missing`` stuff and replaced with a ``__getattr__`` | |
8 | override in ``Git``. | |
9 | ||
10 | 0.1.4 | |
11 | ===== | |
12 | ||
13 | * renamed ``git_python`` to ``git``. Be sure to delete all pyc files before | |
14 | testing. | |
15 | ||
16 | Commit | |
17 | ------ | |
18 | * Fixed problem with commit stats not working under all conditions. | |
19 | ||
20 | Git | |
21 | --- | |
22 | * Renamed module to cmd. | |
23 | ||
24 | * Removed shell escaping completely. | |
25 | ||
26 | * Added support for ``stderr``, ``stdin``, and ``with_status``. | |
27 | ||
28 | * ``git_dir`` is now optional in the constructor for ``git.Git``. Git now | |
29 | falls back to ``os.getcwd()`` when git_dir is not specified. | |
30 | ||
31 | * add a ``with_exceptions`` keyword argument to git commands. | |
32 | ``GitCommandError`` is raised when the exit status is non-zero. | |
33 | ||
34 | * add support for a ``GIT_PYTHON_TRACE`` environment variable. | |
35 | ``GIT_PYTHON_TRACE`` allows us to debug GitPython's usage of git through | |
36 | the use of an environment variable. | |
37 | ||
38 | Tree | |
39 | ---- | |
40 | * Fixed up problem where ``name`` doesn't exist on root of tree. | |
41 | ||
42 | Repo | |
43 | ---- | |
44 | * Corrected problem with creating bare repo. Added ``Repo.create`` alias. | |
45 | ||
46 | 0.1.2 | |
47 | ===== | |
48 | ||
49 | Tree | |
50 | ---- | |
51 | * Corrected problem with ``Tree.__div__`` not working with zero length files. | |
52 | Removed ``__len__`` override and replaced with size instead. Also made size | |
53 | cach properly. This is a breaking change. | |
54 | ||
55 | 0.1.1 | |
56 | ===== | |
57 | Fixed up some urls because I'm a moron | |
58 | ||
59 | 0.1.0 | |
60 | ===== | |
61 | initial release |
0 | Copyright (c) 2008, Michael Trier and contributors | |
1 | All rights reserved. | |
2 | ||
3 | Redistribution and use in source and binary forms, with or without | |
4 | modification, are permitted provided that the following conditions | |
5 | are met: | |
6 | ||
7 | * Redistributions of source code must retain the above copyright | |
8 | notice, this list of conditions and the following disclaimer. | |
9 | ||
10 | * Redistributions in binary form must reproduce the above copyright | |
11 | notice, this list of conditions and the following disclaimer in the | |
12 | documentation and/or other materials provided with the distribution. | |
13 | ||
14 | * Neither the name of the GitPython project nor the names of | |
15 | its contributors may be used to endorse or promote products derived | |
16 | from this software without specific prior written permission. | |
17 | ||
18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
19 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
20 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
21 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
22 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
23 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED | |
24 | TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR | |
25 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF | |
26 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING | |
27 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | |
28 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
29 |
0 | Metadata-Version: 1.0 | |
1 | Name: GitPython | |
2 | Version: 0.1.4.1 | |
3 | Summary: Python Git Library | |
4 | Home-page: http://gitorious.org/projects/git-python/ | |
5 | Author: Michael Trier | |
6 | Author-email: mtrier@gmail.com | |
7 | License: BSD License | |
8 | Description: GitPython is a python library used to interact with Git repositories. | |
9 | ||
10 | GitPython provides object model access to your git repository. Once you have | |
11 | created a repository object, you can traverse it to find parent commit(s), | |
12 | trees, blobs, etc. | |
13 | ||
14 | GitPython is a port of the grit library in Ruby created by | |
15 | Tom Preston-Werner and Chris Wanstrath. | |
16 | ||
17 | Platform: UNKNOWN | |
18 | Classifier: Development Status :: 3 - Alpha | |
19 | Classifier: Intended Audience :: Developers | |
20 | Classifier: License :: OSI Approved :: BSD License | |
21 | Classifier: Programming Language :: Python | |
22 | Classifier: Topic :: Software Development :: Libraries :: Python Modules |
0 | ========== | |
1 | GitPython | |
2 | ========== | |
3 | ||
4 | GitPython is a python library used to interact with Git repositories. | |
5 | ||
6 | GitPython is a port of the grit_ library in Ruby created by | |
7 | Tom Preston-Werner and Chris Wanstrath. | |
8 | ||
9 | .. _grit: http://grit.rubyforge.org | |
10 | ||
11 | REQUIREMENTS | |
12 | ============ | |
13 | ||
14 | * Git_ tested with 1.5.3.7 | |
15 | * `Python Nose`_ - used for running the tests | |
16 | * `Mock by Michael Foord`_ used for tests | |
17 | ||
18 | .. _Git: http://git.or.cz/ | |
19 | .. _Python Nose: http://code.google.com/p/python-nose/ | |
20 | .. _Mock by Michael Foord: http://www.voidspace.org.uk/python/mock.html | |
21 | ||
22 | INSTALL | |
23 | ======= | |
24 | ||
25 | python setup.py install | |
26 | ||
27 | SOURCE | |
28 | ====== | |
29 | ||
30 | GitPython's git repo is available on Gitorious, which can be browsed at: | |
31 | ||
32 | http://gitorious.org/projects/git-python/ | |
33 | ||
34 | and cloned from: | |
35 | ||
36 | git://gitorious.org/git-python/mainline.git | |
37 | ||
38 | LICENSE | |
39 | ======= | |
40 | ||
41 | New BSD License. See the LICENSE file.⏎ |
Binary diff not shown
0 | ======== | |
1 | TUTORIAL | |
2 | ======== | |
3 | ||
4 | GitPython provides object model access to your git repository. Once you have | |
5 | created a repository object, you can traverse it to find parent commit(s), | |
6 | trees, blobs, etc. | |
7 | ||
8 | Initialize a Repo object | |
9 | ************************ | |
10 | ||
11 | The first step is to create a ``Repo`` object to represent your repository. | |
12 | ||
13 | >>> from git import * | |
14 | >>> repo = Repo("/Users/mtrier/Development/git-python") | |
15 | ||
16 | In the above example, the directory ``/Users/mtrier/Development/git-python`` | |
17 | is my working repository and contains the ``.git`` directory. You can also | |
18 | initialize GitPython with a bare repository. | |
19 | ||
20 | >>> repo = Repo.create("/var/git/git-python.git") | |
21 | ||
22 | Getting a list of commits | |
23 | ************************* | |
24 | ||
25 | From the ``Repo`` object, you can get a list of ``Commit`` | |
26 | objects. | |
27 | ||
28 | >>> repo.commits() | |
29 | [<GitPython.Commit "207c0c4418115df0d30820ab1a9acd2ea4bf4431">, | |
30 | <GitPython.Commit "a91c45eee0b41bf3cdaad3418ca3850664c4a4b4">, | |
31 | <GitPython.Commit "e17c7e11aed9e94d2159e549a99b966912ce1091">, | |
32 | <GitPython.Commit "bd795df2d0e07d10e0298670005c0e9d9a5ed867">] | |
33 | ||
34 | Called without arguments, ``Repo.commits`` returns a list of up to ten commits | |
35 | reachable by the master branch (starting at the latest commit). You can ask | |
36 | for commits beginning at a different branch, commit, tag, etc. | |
37 | ||
38 | >>> repo.commits('mybranch') | |
39 | >>> repo.commits('40d3057d09a7a4d61059bca9dca5ae698de58cbe') | |
40 | >>> repo.commits('v0.1') | |
41 | ||
42 | You can specify the maximum number of commits to return. | |
43 | ||
44 | >>> repo.commits('master', 100) | |
45 | ||
46 | If you need paging, you can specify a number of commits to skip. | |
47 | ||
48 | >>> repo.commits('master', 10, 20) | |
49 | ||
50 | The above will return commits 21-30 from the commit list. | |
51 | ||
52 | The Commit object | |
53 | ***************** | |
54 | ||
55 | Commit objects contain information about a specific commit. | |
56 | ||
57 | >>> head = repo.commits()[0] | |
58 | ||
59 | >>> head.id | |
60 | '207c0c4418115df0d30820ab1a9acd2ea4bf4431' | |
61 | ||
62 | >>> head.parents | |
63 | [<GitPython.Commit "a91c45eee0b41bf3cdaad3418ca3850664c4a4b4">] | |
64 | ||
65 | >>> head.tree | |
66 | <GitPython.Tree "563413aedbeda425d8d9dcbb744247d0c3e8a0ac"> | |
67 | ||
68 | >>> head.author | |
69 | <GitPython.Actor "Michael Trier <mtrier@gmail.com>"> | |
70 | ||
71 | >>> head.authored_date | |
72 | (2008, 5, 7, 5, 0, 56, 2, 128, 0) | |
73 | ||
74 | >>> head.committer | |
75 | <GitPython.Actor "Michael Trier <mtrier@gmail.com>"> | |
76 | ||
77 | >>> head.committed_date | |
78 | (2008, 5, 7, 5, 0, 56, 2, 128, 0) | |
79 | ||
80 | >>> head.message | |
81 | 'cleaned up a lot of test information. Fixed escaping so it works with | |
82 | subprocess.' | |
83 | ||
84 | Note: date time is represented in a `struct_time`_ format. Conversion to | |
85 | human readable form can be accomplished with the various time module methods. | |
86 | ||
87 | >>> import time | |
88 | >>> time.asctime(head.committed_date) | |
89 | 'Wed May 7 05:56:02 2008' | |
90 | ||
91 | >>> time.strftime("%a, %d %b %Y %H:%M", head.committed_date) | |
92 | 'Wed, 7 May 2008 05:56' | |
93 | ||
94 | .. _struct_time: http://docs.python.org/lib/module-time.html | |
95 | ||
96 | You can traverse a commit's ancestry by chaining calls to ``parents``. | |
97 | ||
98 | >>> repo.commits()[0].parents[0].parents[0].parents[0] | |
99 | ||
100 | The above corresponds to ``master^^^`` or ``master~3`` in git parlance. | |
101 | ||
102 | The Tree object | |
103 | *************** | |
104 | ||
105 | A tree records pointers to the contents of a directory. Let's say you want | |
106 | the root tree of the latest commit on the master branch. | |
107 | ||
108 | >>> tree = repo.commits()[0].tree | |
109 | <GitPython.Tree "a006b5b1a8115185a228b7514cdcd46fed90dc92"> | |
110 | ||
111 | >>> tree.id | |
112 | 'a006b5b1a8115185a228b7514cdcd46fed90dc92' | |
113 | ||
114 | Once you have a tree, you can get the contents. | |
115 | ||
116 | >>> contents = tree.contents | |
117 | [<GitPython.Blob "6a91a439ea968bf2f5ce8bb1cd8ddf5bf2cad6c7">, | |
118 | <GitPython.Blob "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391">, | |
119 | <GitPython.Tree "eaa0090ec96b054e425603480519e7cf587adfc3">, | |
120 | <GitPython.Blob "980e72ae16b5378009ba5dfd6772b59fe7ccd2df">] | |
121 | ||
122 | This tree contains three ``Blob`` objects and one ``Tree`` object. The trees | |
123 | are subdirectories and the blobs are files. Trees below the root have | |
124 | additional attributes. | |
125 | ||
126 | >>> contents = tree.contents[-2] | |
127 | <GitPython.Tree "e5445b9db4a9f08d5b4de4e29e61dffda2f386ba"> | |
128 | ||
129 | >>> contents.name | |
130 | 'test' | |
131 | ||
132 | >>> contents.mode | |
133 | '040000' | |
134 | ||
135 | There is a convenience method that allows you to get a named sub-object | |
136 | from a tree. | |
137 | ||
138 | >>> tree/"lib" | |
139 | <GitPython.Tree "c1c7214dde86f76bc3e18806ac1f47c38b2b7a30"> | |
140 | ||
141 | You can also get a tree directly from the repository if you know its name. | |
142 | ||
143 | >>> repo.tree() | |
144 | <GitPython.Tree "master"> | |
145 | ||
146 | >>> repo.tree("c1c7214dde86f76bc3e18806ac1f47c38b2b7a30") | |
147 | <GitPython.Tree "c1c7214dde86f76bc3e18806ac1f47c38b2b7a30"> | |
148 | ||
149 | The Blob object | |
150 | *************** | |
151 | ||
152 | A blob represents a file. Trees often contain blobs. | |
153 | ||
154 | >>> blob = tree.contents[-1] | |
155 | <GitPython.Blob "b19574431a073333ea09346eafd64e7b1908ef49"> | |
156 | ||
157 | A blob has certain attributes. | |
158 | ||
159 | >>> blob.name | |
160 | 'urls.py' | |
161 | ||
162 | >>> blob.mode | |
163 | '100644' | |
164 | ||
165 | >>> blob.mime_type | |
166 | 'text/x-python' | |
167 | ||
168 | >>> blob.size | |
169 | 415 | |
170 | ||
171 | You can get the data of a blob as a string. | |
172 | ||
173 | >>> blob.data | |
174 | "from django.conf.urls.defaults import *\nfrom django.conf..." | |
175 | ||
176 | You can also get a blob directly from the repo if you know its name. | |
177 | ||
178 | >>> repo.blob("b19574431a073333ea09346eafd64e7b1908ef49") | |
179 | <GitPython.Blob "b19574431a073333ea09346eafd64e7b1908ef49"> | |
180 | ||
181 | What Else? | |
182 | ********** | |
183 | ||
184 | There is more stuff in there, like the ability to tar or gzip repos, stats, | |
185 | log, blame, and probably a few other things. Additionally calls to the git | |
186 | instance are handled through a ``__getattr__`` construct, which makes | |
187 | available any git commands directly, with a nice conversion of Python dicts | |
188 | to command line parameters. | |
189 | ||
190 | Check the unit tests, they're pretty exhaustive. |
0 | Metadata-Version: 1.0 | |
1 | Name: GitPython | |
2 | Version: 0.1.4.1 | |
3 | Summary: Python Git Library | |
4 | Home-page: http://gitorious.org/projects/git-python/ | |
5 | Author: Michael Trier | |
6 | Author-email: mtrier@gmail.com | |
7 | License: BSD License | |
8 | Description: GitPython is a python library used to interact with Git repositories. | |
9 | ||
10 | GitPython provides object model access to your git repository. Once you have | |
11 | created a repository object, you can traverse it to find parent commit(s), | |
12 | trees, blobs, etc. | |
13 | ||
14 | GitPython is a port of the grit library in Ruby created by | |
15 | Tom Preston-Werner and Chris Wanstrath. | |
16 | ||
17 | Platform: UNKNOWN | |
18 | Classifier: Development Status :: 3 - Alpha | |
19 | Classifier: Intended Audience :: Developers | |
20 | Classifier: License :: OSI Approved :: BSD License | |
21 | Classifier: Programming Language :: Python | |
22 | Classifier: Topic :: Software Development :: Libraries :: Python Modules |
0 | AUTHORS | |
1 | CHANGES | |
2 | LICENSE | |
3 | MANIFEST.in | |
4 | README | |
5 | VERSION | |
6 | setup.py | |
7 | doc/tutorial.txt | |
8 | lib/GitPython.egg-info/PKG-INFO | |
9 | lib/GitPython.egg-info/SOURCES.txt | |
10 | lib/GitPython.egg-info/dependency_links.txt | |
11 | lib/GitPython.egg-info/top_level.txt | |
12 | lib/git/__init__.py | |
13 | lib/git/actor.py | |
14 | lib/git/blob.py | |
15 | lib/git/cmd.py | |
16 | lib/git/commit.py | |
17 | lib/git/diff.py | |
18 | lib/git/errors.py | |
19 | lib/git/head.py | |
20 | lib/git/lazy.py | |
21 | lib/git/repo.py | |
22 | lib/git/stats.py | |
23 | lib/git/tag.py | |
24 | lib/git/tree.py | |
25 | lib/git/utils.py |
0 | git |
Binary diff not shown
Binary diff not shown
0 | # __init__.py | |
1 | # Copyright (C) 2008 Michael Trier (mtrier@gmail.com) and contributors | |
2 | # | |
3 | # This module is part of GitPython and is released under | |
4 | # the BSD License: http://www.opensource.org/licenses/bsd-license.php | |
5 | ||
6 | import os | |
7 | import inspect | |
8 | ||
9 | __version__ = '0.1.4.1' | |
10 | ||
11 | from git.actor import Actor | |
12 | from git.blob import Blob | |
13 | from git.commit import Commit | |
14 | from git.diff import Diff | |
15 | from git.errors import InvalidGitRepositoryError, NoSuchPathError, GitCommandError | |
16 | from git.cmd import Git | |
17 | from git.head import Head | |
18 | from git.repo import Repo | |
19 | from git.stats import Stats | |
20 | from git.tag import Tag | |
21 | from git.tree import Tree | |
22 | from git.utils import dashify | |
23 | from git.utils import touch | |
24 | ||
25 | __all__ = [ name for name, obj in locals().items() | |
26 | if not (name.startswith('_') or inspect.ismodule(obj)) ] |
0 | # actor.py | |
1 | # Copyright (C) 2008 Michael Trier (mtrier@gmail.com) and contributors | |
2 | # | |
3 | # This module is part of GitPython and is released under | |
4 | # the BSD License: http://www.opensource.org/licenses/bsd-license.php | |
5 | ||
6 | import re | |
7 | ||
8 | class Actor(object): | |
9 | def __init__(self, name, email): | |
10 | self.name = name | |
11 | self.email = email | |
12 | ||
13 | def __str__(self): | |
14 | return self.name | |
15 | ||
16 | def __repr__(self): | |
17 | return '<GitPython.Actor "%s <%s>">' % (self.name, self.email) | |
18 | ||
19 | @classmethod | |
20 | def from_string(cls, string): | |
21 | """ | |
22 | Create an Actor from a string. | |
23 | ||
24 | ``str`` | |
25 | is the string, which is expected to be in regular git format | |
26 | ||
27 | Format | |
28 | John Doe <jdoe@example.com> | |
29 | ||
30 | Returns | |
31 | Actor | |
32 | """ | |
33 | if re.search(r'<.+>', string): | |
34 | m = re.search(r'(.*) <(.+?)>', string) | |
35 | name, email = m.groups() | |
36 | return Actor(name, email) | |
37 | else: | |
38 | return Actor(string, None) |
0 | # blob.py | |
1 | # Copyright (C) 2008 Michael Trier (mtrier@gmail.com) and contributors | |
2 | # | |
3 | # This module is part of GitPython and is released under | |
4 | # the BSD License: http://www.opensource.org/licenses/bsd-license.php | |
5 | ||
6 | import mimetypes | |
7 | import os | |
8 | import re | |
9 | import time | |
10 | from actor import Actor | |
11 | from commit import Commit | |
12 | ||
13 | class Blob(object): | |
14 | DEFAULT_MIME_TYPE = "text/plain" | |
15 | ||
16 | def __init__(self, repo, **kwargs): | |
17 | """ | |
18 | Create an unbaked Blob containing just the specified attributes | |
19 | ||
20 | ``repo`` | |
21 | is the Repo | |
22 | ||
23 | ``atts`` | |
24 | is a dict of instance variable data | |
25 | ||
26 | Returns | |
27 | GitPython.Blob | |
28 | """ | |
29 | self.id = None | |
30 | self.mode = None | |
31 | self.name = None | |
32 | self._size = None | |
33 | self.data_stored = None | |
34 | ||
35 | self.repo = repo | |
36 | for k, v in kwargs.items(): | |
37 | setattr(self, k, v) | |
38 | ||
39 | @property | |
40 | def size(self): | |
41 | """ | |
42 | The size of this blob in bytes | |
43 | ||
44 | Returns | |
45 | int | |
46 | """ | |
47 | if self._size is None: | |
48 | self._size = int(self.repo.git.cat_file(self.id, **{'s': True}).rstrip()) | |
49 | return self._size | |
50 | ||
51 | @property | |
52 | def data(self): | |
53 | """ | |
54 | The binary contents of this blob. | |
55 | ||
56 | Returns | |
57 | str | |
58 | """ | |
59 | self.data_stored = self.data_stored or self.repo.git.cat_file(self.id, **{'p': True}) | |
60 | return self.data_stored | |
61 | ||
62 | @property | |
63 | def mime_type(self): | |
64 | """ | |
65 | The mime type of this file (based on the filename) | |
66 | ||
67 | Returns | |
68 | str | |
69 | """ | |
70 | guesses = None | |
71 | if self.name: | |
72 | guesses = mimetypes.guess_type(self.name) | |
73 | return guesses and guesses[0] or self.DEFAULT_MIME_TYPE | |
74 | ||
75 | @property | |
76 | def basename(self): | |
77 | return os.path.basename(self.name) | |
78 | ||
79 | @classmethod | |
80 | def blame(cls, repo, commit, file): | |
81 | """ | |
82 | The blame information for the given file at the given commit | |
83 | ||
84 | Returns | |
85 | list: [GitPython.Commit, list: [<line>]] | |
86 | """ | |
87 | data = repo.git.blame(commit, '--', file, **{'p': True}) | |
88 | commits = {} | |
89 | blames = [] | |
90 | info = None | |
91 | ||
92 | for line in data.splitlines(): | |
93 | parts = re.split(r'\s+', line, 1) | |
94 | if re.search(r'^[0-9A-Fa-f]{40}$', parts[0]): | |
95 | if re.search(r'^([0-9A-Fa-f]{40}) (\d+) (\d+) (\d+)$', line): | |
96 | m = re.search(r'^([0-9A-Fa-f]{40}) (\d+) (\d+) (\d+)$', line) | |
97 | id, origin_line, final_line, group_lines = m.groups() | |
98 | info = {'id': id} | |
99 | blames.append([None, []]) | |
100 | elif re.search(r'^([0-9A-Fa-f]{40}) (\d+) (\d+)$', line): | |
101 | m = re.search(r'^([0-9A-Fa-f]{40}) (\d+) (\d+)$', line) | |
102 | id, origin_line, final_line = m.groups() | |
103 | info = {'id': id} | |
104 | elif re.search(r'^(author|committer)', parts[0]): | |
105 | if re.search(r'^(.+)-mail$', parts[0]): | |
106 | m = re.search(r'^(.+)-mail$', parts[0]) | |
107 | info["%s_email" % m.groups()[0]] = parts[-1] | |
108 | elif re.search(r'^(.+)-time$', parts[0]): | |
109 | m = re.search(r'^(.+)-time$', parts[0]) | |
110 | info["%s_date" % m.groups()[0]] = time.gmtime(int(parts[-1])) | |
111 | elif re.search(r'^(author|committer)$', parts[0]): | |
112 | m = re.search(r'^(author|committer)$', parts[0]) | |
113 | info[m.groups()[0]] = parts[-1] | |
114 | elif re.search(r'^filename', parts[0]): | |
115 | info['filename'] = parts[-1] | |
116 | elif re.search(r'^summary', parts[0]): | |
117 | info['summary'] = parts[-1] | |
118 | elif parts[0] == '': | |
119 | if info: | |
120 | c = commits.has_key(info['id']) and commits[info['id']] | |
121 | if not c: | |
122 | c = Commit(repo, **{'id': info['id'], | |
123 | 'author': Actor.from_string(info['author'] + ' ' + info['author_email']), | |
124 | 'authored_date': info['author_date'], | |
125 | 'committer': Actor.from_string(info['committer'] + ' ' + info['committer_email']), | |
126 | 'committed_date': info['committer_date'], | |
127 | 'message': info['summary']}) | |
128 | commits[info['id']] = c | |
129 | ||
130 | m = re.search(r'^\t(.*)$', line) | |
131 | text, = m.groups() | |
132 | blames[-1][0] = c | |
133 | blames[-1][1] += text | |
134 | info = None | |
135 | ||
136 | return blames | |
137 | ||
138 | def __repr__(self): | |
139 | return '<GitPython.Blob "%s">' % self.id |
0 | # cmd.py | |
1 | # Copyright (C) 2008 Michael Trier (mtrier@gmail.com) and contributors | |
2 | # | |
3 | # This module is part of GitPython and is released under | |
4 | # the BSD License: http://www.opensource.org/licenses/bsd-license.php | |
5 | ||
6 | import os | |
7 | import subprocess | |
8 | import re | |
9 | from utils import * | |
10 | from errors import GitCommandError | |
11 | ||
12 | # Enables debugging of GitPython's git commands | |
13 | GIT_PYTHON_TRACE = os.environ.get("GIT_PYTHON_TRACE", False) | |
14 | ||
15 | execute_kwargs = ('istream', 'with_keep_cwd', 'with_extended_output', | |
16 | 'with_exceptions', 'with_raw_output') | |
17 | ||
18 | class Git(object): | |
19 | """ | |
20 | The Git class manages communication with the Git binary | |
21 | """ | |
22 | def __init__(self, git_dir): | |
23 | super(Git, self).__init__() | |
24 | self.git_dir = git_dir | |
25 | ||
26 | def __getattr__(self, name): | |
27 | if name[:1] == '_': | |
28 | raise AttributeError(name) | |
29 | return lambda *args, **kwargs: self._call_process(name, *args, **kwargs) | |
30 | ||
31 | @property | |
32 | def get_dir(self): | |
33 | return self.git_dir | |
34 | ||
35 | def execute(self, command, | |
36 | istream=None, | |
37 | with_keep_cwd=False, | |
38 | with_extended_output=False, | |
39 | with_exceptions=True, | |
40 | with_raw_output=False, | |
41 | ): | |
42 | """ | |
43 | Handles executing the command on the shell and consumes and returns | |
44 | the returned information (stdout) | |
45 | ||
46 | ``command`` | |
47 | The command argument list to execute | |
48 | ||
49 | ``istream`` | |
50 | Standard input filehandle passed to subprocess.Popen. | |
51 | ||
52 | ``with_keep_cwd`` | |
53 | Whether to use the current working directory from os.getcwd(). | |
54 | GitPython uses get_work_tree() as its working directory by | |
55 | default and get_git_dir() for bare repositories. | |
56 | ||
57 | ``with_extended_output`` | |
58 | Whether to return a (status, stdout, stderr) tuple. | |
59 | ||
60 | ``with_exceptions`` | |
61 | Whether to raise an exception when git returns a non-zero status. | |
62 | ||
63 | ``with_raw_output`` | |
64 | Whether to avoid stripping off trailing whitespace. | |
65 | ||
66 | Returns | |
67 | str(output) # extended_output = False (Default) | |
68 | tuple(int(status), str(output)) # extended_output = True | |
69 | """ | |
70 | ||
71 | if GIT_PYTHON_TRACE and not GIT_PYTHON_TRACE == 'full': | |
72 | print ' '.join(command) | |
73 | ||
74 | # Allow the user to have the command executed in their working dir. | |
75 | if with_keep_cwd or self.git_dir is None: | |
76 | cwd = os.getcwd() | |
77 | else: | |
78 | cwd=self.git_dir | |
79 | ||
80 | # Start the process | |
81 | proc = subprocess.Popen(command, | |
82 | cwd=cwd, | |
83 | stdin=istream, | |
84 | stderr=subprocess.PIPE, | |
85 | stdout=subprocess.PIPE | |
86 | ) | |
87 | ||
88 | # Wait for the process to return | |
89 | try: | |
90 | stdout_value = proc.stdout.read() | |
91 | stderr_value = proc.stderr.read() | |
92 | status = proc.wait() | |
93 | finally: | |
94 | proc.stdout.close() | |
95 | proc.stderr.close() | |
96 | ||
97 | # Strip off trailing whitespace by default | |
98 | if not with_raw_output: | |
99 | stdout_value = stdout_value.rstrip() | |
100 | stderr_value = stderr_value.rstrip() | |
101 | ||
102 | if with_exceptions and status != 0: | |
103 | raise GitCommandError(command, status, stderr_value) | |
104 | ||
105 | if GIT_PYTHON_TRACE == 'full': | |
106 | if stderr_value: | |
107 | print "%s -> %d: '%s' !! '%s'" % (command, status, stdout_value, stderr_value) | |
108 | elif stdout_value: | |
109 | print "%s -> %d: '%s'" % (command, status, stdout_value) | |
110 | else: | |
111 | print "%s -> %d" % (command, status) | |
112 | ||
113 | # Allow access to the command's status code | |
114 | if with_extended_output: | |
115 | return (status, stdout_value, stderr_value) | |
116 | else: | |
117 | return stdout_value | |
118 | ||
119 | def transform_kwargs(self, **kwargs): | |
120 | """ | |
121 | Transforms Python style kwargs into git command line options. | |
122 | """ | |
123 | args = [] | |
124 | for k, v in kwargs.items(): | |
125 | if len(k) == 1: | |
126 | if v is True: | |
127 | args.append("-%s" % k) | |
128 | elif type(v) is not bool: | |
129 | args.append("-%s%s" % (k, v)) | |
130 | else: | |
131 | if v is True: | |
132 | args.append("--%s" % dashify(k)) | |
133 | elif type(v) is not bool: | |
134 | args.append("--%s=%s" % (dashify(k), v)) | |
135 | return args | |
136 | ||
137 | def _call_process(self, method, *args, **kwargs): | |
138 | """ | |
139 | Run the given git command with the specified arguments and return | |
140 | the result as a String | |
141 | ||
142 | ``method`` | |
143 | is the command | |
144 | ||
145 | ``args`` | |
146 | is the list of arguments | |
147 | ||
148 | ``kwargs`` | |
149 | is a dict of keyword arguments. | |
150 | This function accepts the same optional keyword arguments | |
151 | as execute(). | |
152 | ||
153 | Examples | |
154 | git.rev_list('master', max_count=10, header=True) | |
155 | ||
156 | Returns | |
157 | Same as execute() | |
158 | """ | |
159 | ||
160 | # Handle optional arguments prior to calling transform_kwargs | |
161 | # otherwise these'll end up in args, which is bad. | |
162 | _kwargs = {} | |
163 | for kwarg in execute_kwargs: | |
164 | try: | |
165 | _kwargs[kwarg] = kwargs.pop(kwarg) | |
166 | except KeyError: | |
167 | pass | |
168 | ||
169 | # Prepare the argument list | |
170 | opt_args = self.transform_kwargs(**kwargs) | |
171 | ext_args = map(str, args) | |
172 | args = opt_args + ext_args | |
173 | ||
174 | call = ["git", dashify(method)] | |
175 | call.extend(args) | |
176 | ||
177 | return self.execute(call, **_kwargs) |
0 | # commit.py | |
1 | # Copyright (C) 2008 Michael Trier (mtrier@gmail.com) and contributors | |
2 | # | |
3 | # This module is part of GitPython and is released under | |
4 | # the BSD License: http://www.opensource.org/licenses/bsd-license.php | |
5 | ||
6 | import re | |
7 | import time | |
8 | ||
9 | from actor import Actor | |
10 | from lazy import LazyMixin | |
11 | import tree | |
12 | import diff | |
13 | import stats | |
14 | ||
15 | class Commit(LazyMixin): | |
16 | def __init__(self, repo, **kwargs): | |
17 | """ | |
18 | Instantiate a new Commit | |
19 | ||
20 | ``id`` | |
21 | is the id of the commit | |
22 | ||
23 | ``parents`` | |
24 | is a list of commit ids (will be converted into Commit instances) | |
25 | ||
26 | ``tree`` | |
27 | is the correspdonding tree id (will be converted into a Tree object) | |
28 | ||
29 | ``author`` | |
30 | is the author string | |
31 | ||
32 | ``authored_date`` | |
33 | is the authored DateTime | |
34 | ||
35 | ``committer`` | |
36 | is the committer string | |
37 | ||
38 | ``committed_date`` | |
39 | is the committed DateTime | |
40 | ||
41 | ``message`` | |
42 | is the first line of the commit message | |
43 | ||
44 | Returns | |
45 | GitPython.Commit | |
46 | """ | |
47 | LazyMixin.__init__(self) | |
48 | ||
49 | self.repo = repo | |
50 | self.id = None | |
51 | self.tree = None | |
52 | self.author = None | |
53 | self.authored_date = None | |
54 | self.committer = None | |
55 | self.committed_date = None | |
56 | self.message = None | |
57 | self.parents = None | |
58 | ||
59 | for k, v in kwargs.items(): | |
60 | setattr(self, k, v) | |
61 | ||
62 | if self.id: | |
63 | if 'parents' in kwargs: | |
64 | self.parents = map(lambda p: Commit(repo, **{'id': p}), kwargs['parents']) | |
65 | if 'tree' in kwargs: | |
66 | self.tree = tree.Tree(repo, **{'id': kwargs['tree']}) | |
67 | ||
68 | def __bake__(self): | |
69 | temp = Commit.find_all(self.repo, self.id, **{'max_count': 1})[0] | |
70 | self.parents = temp.parents | |
71 | self.tree = temp.tree | |
72 | self.author = temp.author | |
73 | self.authored_date = temp.authored_date | |
74 | self.committer = temp.committer | |
75 | self.committed_date = temp.committed_date | |
76 | self.message = temp.message | |
77 | ||
78 | @property | |
79 | def id_abbrev(self): | |
80 | return self.id[0:7] | |
81 | ||
82 | @classmethod | |
83 | def count(cls, repo, ref): | |
84 | """ | |
85 | Count the number of commits reachable from this ref | |
86 | ||
87 | ``repo`` | |
88 | is the Repo | |
89 | ||
90 | ``ref`` | |
91 | is the ref from which to begin (SHA1 or name) | |
92 | ||
93 | Returns | |
94 | int | |
95 | """ | |
96 | return len(repo.git.rev_list(ref).strip().splitlines()) | |
97 | ||
98 | @classmethod | |
99 | def find_all(cls, repo, ref, **kwargs): | |
100 | """ | |
101 | Find all commits matching the given criteria. | |
102 | ``repo`` | |
103 | is the Repo | |
104 | ||
105 | ``ref`` | |
106 | is the ref from which to begin (SHA1 or name) | |
107 | ||
108 | ``options`` | |
109 | is a Hash of optional arguments to git where | |
110 | ``max_count`` is the maximum number of commits to fetch | |
111 | ``skip`` is the number of commits to skip | |
112 | ||
113 | Returns | |
114 | GitPython.Commit[] | |
115 | """ | |
116 | options = {'pretty': 'raw'} | |
117 | options.update(kwargs) | |
118 | ||
119 | output = repo.git.rev_list(ref, **options) | |
120 | return cls.list_from_string(repo, output) | |
121 | ||
122 | @classmethod | |
123 | def list_from_string(cls, repo, text): | |
124 | """ | |
125 | Parse out commit information into a list of Commit objects | |
126 | ||
127 | ``repo`` | |
128 | is the Repo | |
129 | ||
130 | ``text`` | |
131 | is the text output from the git command (raw format) | |
132 | ||
133 | Returns | |
134 | GitPython.Commit[] | |
135 | """ | |
136 | lines = [l for l in text.splitlines() if l.strip()] | |
137 | ||
138 | commits = [] | |
139 | ||
140 | while lines: | |
141 | id = lines.pop(0).split()[-1] | |
142 | tree = lines.pop(0).split()[-1] | |
143 | ||
144 | parents = [] | |
145 | while lines and re.search(r'^parent', lines[0]): | |
146 | parents.append(lines.pop(0).split()[-1]) | |
147 | author, authored_date = cls.actor(lines.pop(0)) | |
148 | committer, committed_date = cls.actor(lines.pop(0)) | |
149 | ||
150 | messages = [] | |
151 | while lines and re.search(r'^ {4}', lines[0]): | |
152 | messages.append(lines.pop(0).strip()) | |
153 | ||
154 | message = messages and messages[0] or '' | |
155 | ||
156 | commits.append(Commit(repo, id=id, parents=parents, tree=tree, author=author, authored_date=authored_date, | |
157 | committer=committer, committed_date=committed_date, message=message)) | |
158 | ||
159 | return commits | |
160 | ||
161 | @classmethod | |
162 | def diff(cls, repo, a, b = None, paths = None): | |
163 | """ | |
164 | Show diffs between two trees: | |
165 | ||
166 | ``repo`` | |
167 | is the Repo | |
168 | ||
169 | ``a`` | |
170 | is a named commit | |
171 | ||
172 | ``b`` | |
173 | is an optional named commit. Passing a list assumes you | |
174 | wish to omit the second named commit and limit the diff to the | |
175 | given paths. | |
176 | ||
177 | ``paths`` | |
178 | is a list of paths to limit the diff. | |
179 | ||
180 | Returns | |
181 | GitPython.Diff[] | |
182 | """ | |
183 | paths = paths or [] | |
184 | ||
185 | if isinstance(b, list): | |
186 | paths = b | |
187 | b = None | |
188 | ||
189 | if paths: | |
190 | paths.insert(0, "--") | |
191 | ||
192 | if b: | |
193 | paths.insert(0, b) | |
194 | paths.insert(0, a) | |
195 | text = repo.git.diff(*paths, **{'full_index': True}) | |
196 | return diff.Diff.list_from_string(repo, text) | |
197 | ||
198 | @property | |
199 | def diffs(self): | |
200 | if not self.parents: | |
201 | d = self.repo.git.show(self.id, **{'full_index': True, 'pretty': 'raw'}) | |
202 | if re.search(r'diff --git a', d): | |
203 | if not re.search(r'^diff --git a', d): | |
204 | p = re.compile(r'.+?(diff --git a)', re.MULTILINE | re.DOTALL) | |
205 | d = p.sub(r'diff --git a', d, 1) | |
206 | else: | |
207 | d = '' | |
208 | return diff.Diff.list_from_string(self.repo, d) | |
209 | else: | |
210 | return self.diff(self.repo, self.parents[0].id, self.id) | |
211 | ||
212 | @property | |
213 | def stats(self): | |
214 | if not self.parents: | |
215 | text = self.repo.git.diff(self.id, **{'numstat': True}) | |
216 | text2 = "" | |
217 | for line in text.splitlines(): | |
218 | (insertions, deletions, filename) = line.split("\t") | |
219 | text2 += "%s\t%s\t%s\n" % (deletions, insertions, filename) | |
220 | text = text2 | |
221 | else: | |
222 | text = self.repo.git.diff(self.parents[0].id, self.id, **{'numstat': True}) | |
223 | return stats.Stats.list_from_string(self.repo, text) | |
224 | ||
225 | def __str__(self): | |
226 | """ Convert commit to string which is SHA1 """ | |
227 | return self.id | |
228 | ||
229 | def __repr__(self): | |
230 | return '<GitPython.Commit "%s">' % self.id | |
231 | ||
232 | @classmethod | |
233 | def actor(cls, line): | |
234 | """ | |
235 | Parse out the actor (author or committer) info | |
236 | ||
237 | Returns | |
238 | [str (actor name and email), time (acted at time)] | |
239 | """ | |
240 | m = re.search(r'^.+? (.*) (\d+) .*$', line) | |
241 | actor, epoch = m.groups() | |
242 | return [Actor.from_string(actor), time.gmtime(int(epoch))] |
0 | # diff.py | |
1 | # Copyright (C) 2008 Michael Trier (mtrier@gmail.com) and contributors | |
2 | # | |
3 | # This module is part of GitPython and is released under | |
4 | # the BSD License: http://www.opensource.org/licenses/bsd-license.php | |
5 | ||
6 | import re | |
7 | import commit | |
8 | ||
9 | class Diff(object): | |
10 | """ | |
11 | A Diff contains diff information between two commits. | |
12 | """ | |
13 | ||
14 | def __init__(self, repo, a_path, b_path, a_commit, b_commit, a_mode, b_mode, new_file, deleted_file, diff): | |
15 | self.repo = repo | |
16 | self.a_path = a_path | |
17 | self.b_path = b_path | |
18 | ||
19 | if not a_commit or re.search(r'^0{40}$', a_commit): | |
20 | self.a_commit = None | |
21 | else: | |
22 | self.a_commit = commit.Commit(repo, **{'id': a_commit}) | |
23 | if not b_commit or re.search(r'^0{40}$', b_commit): | |
24 | self.b_commit = None | |
25 | else: | |
26 | self.b_commit = commit.Commit(repo, **{'id': b_commit}) | |
27 | ||
28 | self.a_mode = a_mode | |
29 | self.b_mode = b_mode | |
30 | self.new_file = new_file | |
31 | self.deleted_file = deleted_file | |
32 | self.diff = diff | |
33 | ||
34 | @classmethod | |
35 | def list_from_string(cls, repo, text): | |
36 | lines = text.splitlines() | |
37 | a_mode = None | |
38 | b_mode = None | |
39 | diffs = [] | |
40 | while lines: | |
41 | m = re.search(r'^diff --git a/(\S+) b/(\S+)$', lines.pop(0)) | |
42 | if m: | |
43 | a_path, b_path = m.groups() | |
44 | if re.search(r'^old mode', lines[0]): | |
45 | m = re.search(r'^old mode (\d+)', lines.pop(0)) | |
46 | if m: | |
47 | a_mode, = m.groups() | |
48 | m = re.search(r'^new mode (\d+)', lines.pop(0)) | |
49 | if m: | |
50 | b_mode, = m.groups() | |
51 | if re.search(r'^diff --git', lines[0]): | |
52 | diffs.append(Diff(repo, a_path, b_path, None, None, a_mode, b_mode, False, False, None)) | |
53 | continue | |
54 | ||
55 | new_file = False | |
56 | deleted_file = False | |
57 | ||
58 | if re.search(r'^new file', lines[0]): | |
59 | m = re.search(r'^new file mode (.+)', lines.pop(0)) | |
60 | if m: | |
61 | b_mode, = m.groups() | |
62 | a_mode = None | |
63 | new_file = True | |
64 | elif re.search(r'^deleted file', lines[0]): | |
65 | m = re.search(r'^deleted file mode (.+)$', lines.pop(0)) | |
66 | if m: | |
67 | a_mode, = m.groups() | |
68 | b_mode = None | |
69 | deleted_file = True | |
70 | ||
71 | m = re.search(r'^index ([0-9A-Fa-f]+)\.\.([0-9A-Fa-f]+) ?(.+)?$', lines.pop(0)) | |
72 | if m: | |
73 | a_commit, b_commit, b_mode = m.groups() | |
74 | if b_mode: | |
75 | b_mode = b_mode.strip() | |
76 | ||
77 | diff_lines = [] | |
78 | while lines and not re.search(r'^diff', lines[0]): | |
79 | diff_lines.append(lines.pop(0)) | |
80 | ||
81 | diff = "\n".join(diff_lines) | |
82 | diffs.append(Diff(repo, a_path, b_path, a_commit, b_commit, a_mode, b_mode, new_file, deleted_file, diff)) | |
83 | ||
84 | return diffs |
0 | # errors.py | |
1 | # Copyright (C) 2008 Michael Trier (mtrier@gmail.com) and contributors | |
2 | # | |
3 | # This module is part of GitPython and is released under | |
4 | # the BSD License: http://www.opensource.org/licenses/bsd-license.php | |
5 | ||
6 | class InvalidGitRepositoryError(Exception): | |
7 | pass | |
8 | ||
9 | class NoSuchPathError(Exception): | |
10 | pass | |
11 | ||
12 | class GitCommandError(Exception): | |
13 | def __init__(self, command, status, stderr=None): | |
14 | self.stderr = stderr | |
15 | self.status = status | |
16 | self.command = command | |
17 | ||
18 | def __str__(self): | |
19 | return repr("%s returned exit status %d" % | |
20 | (str(self.command), self.status)) | |
21 |
0 | # head.py | |
1 | # Copyright (C) 2008 Michael Trier (mtrier@gmail.com) and contributors | |
2 | # | |
3 | # This module is part of GitPython and is released under | |
4 | # the BSD License: http://www.opensource.org/licenses/bsd-license.php | |
5 | ||
6 | import commit | |
7 | ||
8 | class Head(object): | |
9 | """ | |
10 | A Head is a named reference to a Commit. Every Head instance contains a name | |
11 | and a Commit object. | |
12 | ||
13 | Examples:: | |
14 | ||
15 | >>> repo = Repo("/path/to/repo") | |
16 | >>> head = repo.heads[0] | |
17 | ||
18 | >>> head.name | |
19 | 'master' | |
20 | ||
21 | >>> head.commit | |
22 | <GitPython.Commit "1c09f116cbc2cb4100fb6935bb162daa4723f455"> | |
23 | ||
24 | >>> head.commit.id | |
25 | '1c09f116cbc2cb4100fb6935bb162daa4723f455' | |
26 | """ | |
27 | ||
28 | def __init__(self, name, commit): | |
29 | """ | |
30 | Instantiate a new Head | |
31 | ||
32 | `name` | |
33 | is the name of the head | |
34 | ||
35 | `commit` | |
36 | is the Commit that the head points to | |
37 | ||
38 | Returns | |
39 | GitPython.Head | |
40 | """ | |
41 | self.name = name | |
42 | self.commit = commit | |
43 | ||
44 | @classmethod | |
45 | def find_all(cls, repo, **kwargs): | |
46 | """ | |
47 | Find all Heads | |
48 | ||
49 | `repo` | |
50 | is the Repo | |
51 | ||
52 | `kwargs` | |
53 | is a dict of options | |
54 | ||
55 | Returns | |
56 | GitPython.Head[] | |
57 | """ | |
58 | ||
59 | options = {'sort': "committerdate", | |
60 | 'format': "%(refname)%00%(objectname)"} | |
61 | options.update(kwargs) | |
62 | ||
63 | output = repo.git.for_each_ref("refs/heads", **options) | |
64 | return cls.list_from_string(repo, output) | |
65 | ||
66 | @classmethod | |
67 | def list_from_string(cls, repo, text): | |
68 | """ | |
69 | Parse out head information into an array of baked head objects | |
70 | ||
71 | ``repo`` | |
72 | is the Repo | |
73 | ``text`` | |
74 | is the text output from the git command | |
75 | ||
76 | Returns | |
77 | GitPython.Head[] | |
78 | """ | |
79 | heads = [] | |
80 | ||
81 | for line in text.splitlines(): | |
82 | heads.append(cls.from_string(repo, line)) | |
83 | ||
84 | return heads | |
85 | ||
86 | @classmethod | |
87 | def from_string(cls, repo, line): | |
88 | """ | |
89 | Create a new Head instance from the given string. | |
90 | ||
91 | ``repo`` | |
92 | is the Repo | |
93 | ||
94 | ``line`` | |
95 | is the formatted head information | |
96 | ||
97 | Format | |
98 | name: [a-zA-Z_/]+ | |
99 | <null byte> | |
100 | id: [0-9A-Fa-f]{40} | |
101 | ||
102 | Returns | |
103 | GitPython.Head | |
104 | """ | |
105 | print line | |
106 | full_name, ids = line.split("\x00") | |
107 | name = full_name.split("/")[-1] | |
108 | c = commit.Commit(repo, **{'id': ids}) | |
109 | return Head(name, c) | |
110 | ||
111 | def __repr__(self): | |
112 | return '<GitPython.Head "%s">' % self.name |
0 | # lazy.py | |
1 | # Copyright (C) 2008 Michael Trier (mtrier@gmail.com) and contributors | |
2 | # | |
3 | # This module is part of GitPython and is released under | |
4 | # the BSD License: http://www.opensource.org/licenses/bsd-license.php | |
5 | ||
6 | class LazyMixin(object): | |
7 | lazy_properties = [] | |
8 | ||
9 | def __init__(self): | |
10 | self.__baked__ = False | |
11 | ||
12 | def __getattribute__(self, attr): | |
13 | val = object.__getattribute__(self, attr) | |
14 | if val is not None: | |
15 | return val | |
16 | else: | |
17 | self.__prebake__() | |
18 | return object.__getattribute__(self, attr) | |
19 | ||
20 | def __bake__(self): | |
21 | """ This method should be overridden in the derived class. """ | |
22 | raise NotImplementedError(" '__bake__' method has not been implemented.") | |
23 | ||
24 | def __prebake__(self): | |
25 | if self.__baked__: | |
26 | return | |
27 | self.__bake__() | |
28 | self.__baked__ = True | |
29 | ||
30 | def __bake_it__(self): | |
31 | self.__baked__ = True |
0 | # repo.py | |
1 | # Copyright (C) 2008 Michael Trier (mtrier@gmail.com) and contributors | |
2 | # | |
3 | # This module is part of GitPython and is released under | |
4 | # the BSD License: http://www.opensource.org/licenses/bsd-license.php | |
5 | ||
6 | import os | |
7 | import re | |
8 | from errors import InvalidGitRepositoryError, NoSuchPathError | |
9 | from utils import touch, is_git_dir | |
10 | from cmd import Git | |
11 | from head import Head | |
12 | from blob import Blob | |
13 | from tag import Tag | |
14 | from commit import Commit | |
15 | from tree import Tree | |
16 | ||
17 | class Repo(object): | |
18 | DAEMON_EXPORT_FILE = 'git-daemon-export-ok' | |
19 | ||
20 | def __init__(self, path=None): | |
21 | """ | |
22 | Create a new Repo instance | |
23 | ||
24 | ``path`` | |
25 | is the path to either the root git directory or the bare git repo | |
26 | ||
27 | Examples:: | |
28 | ||
29 | repo = Repo("/Users/mtrier/Development/git-python") | |
30 | repo = Repo("/Users/mtrier/Development/git-python.git") | |
31 | ||
32 | Returns | |
33 | ``GitPython.Repo`` | |
34 | """ | |
35 | ||
36 | epath = os.path.abspath(os.path.expanduser(path or os.getcwd())) | |
37 | ||
38 | if not os.path.exists(epath): | |
39 | raise NoSuchPathError(epath) | |
40 | ||
41 | self.path = None | |
42 | curpath = epath | |
43 | while curpath: | |
44 | if is_git_dir(curpath): | |
45 | self.bare = True | |
46 | self.path, self.wd = curpath | |
47 | break | |
48 | gitpath = os.path.join(curpath, '.git') | |
49 | if is_git_dir(gitpath): | |
50 | self.bare = False | |
51 | self.path = gitpath | |
52 | self.wd = curpath | |
53 | break | |
54 | curpath, dummy = os.path.split(curpath) | |
55 | if not dummy: | |
56 | break | |
57 | ||
58 | if self.path is None: | |
59 | raise InvalidGitRepositoryError(epath) | |
60 | ||
61 | self.git = Git(self.wd) | |
62 | ||
63 | @property | |
64 | def description(self): | |
65 | """ | |
66 | The project's description. Taken verbatim from GIT_REPO/description | |
67 | ||
68 | Returns | |
69 | str | |
70 | """ | |
71 | try: | |
72 | f = open(os.path.join(self.path, 'description')) | |
73 | result = f.read() | |
74 | return result.rstrip() | |
75 | finally: | |
76 | f.close() | |
77 | ||
78 | @property | |
79 | def heads(self): | |
80 | """ | |
81 | A list of ``Head`` objects representing the branch heads in | |
82 | this repo | |
83 | ||
84 | Returns | |
85 | ``GitPython.Head[]`` | |
86 | """ | |
87 | return Head.find_all(self) | |
88 | ||
89 | # alias heads | |
90 | branches = heads | |
91 | ||
92 | @property | |
93 | def tags(self): | |
94 | """ | |
95 | A list of ``Tag`` objects that are available in this repo | |
96 | ||
97 | Returns | |
98 | ``GitPython.Tag[]`` | |
99 | """ | |
100 | return Tag.find_all(self) | |
101 | ||
102 | def commits(self, start = 'master', max_count = 10, skip = 0): | |
103 | """ | |
104 | A list of Commit objects representing the history of a given ref/commit | |
105 | ||
106 | ``start`` | |
107 | is the branch/commit name (default 'master') | |
108 | ||
109 | ``max_count`` | |
110 | is the maximum number of commits to return (default 10) | |
111 | ||
112 | ``skip`` | |
113 | is the number of commits to skip (default 0) | |
114 | ||
115 | Returns | |
116 | ``GitPython.Commit[]`` | |
117 | """ | |
118 | options = {'max_count': max_count, | |
119 | 'skip': skip} | |
120 | ||
121 | return Commit.find_all(self, start, **options) | |
122 | ||
123 | def commits_between(self, frm, to): | |
124 | """ | |
125 | The Commits objects that are reachable via ``to`` but not via ``frm`` | |
126 | Commits are returned in chronological order. | |
127 | ||
128 | ``from`` | |
129 | is the branch/commit name of the younger item | |
130 | ||
131 | ``to`` | |
132 | is the branch/commit name of the older item | |
133 | ||
134 | Returns | |
135 | ``GitPython.Commit[]`` | |
136 | """ | |
137 | return Commit.find_all(self, "%s..%s" % (frm, to)).reverse() | |
138 | ||
139 | def commits_since(self, start = 'master', since = '1970-01-01'): | |
140 | """ | |
141 | The Commits objects that are newer than the specified date. | |
142 | Commits are returned in chronological order. | |
143 | ||
144 | ``start`` | |
145 | is the branch/commit name (default 'master') | |
146 | ||
147 | ``since`` | |
148 | is a string represeting a date/time | |
149 | ||
150 | Returns | |
151 | ``GitPython.Commit[]`` | |
152 | """ | |
153 | options = {'since': since} | |
154 | ||
155 | return Commit.find_all(self, start, **options) | |
156 | ||
157 | def commit_count(self, start = 'master'): | |
158 | """ | |
159 | The number of commits reachable by the given branch/commit | |
160 | ||
161 | ``start`` | |
162 | is the branch/commit name (default 'master') | |
163 | ||
164 | Returns | |
165 | int | |
166 | """ | |
167 | return Commit.count(self, start) | |
168 | ||
169 | def commit(self, id): | |
170 | """ | |
171 | The Commit object for the specified id | |
172 | ||
173 | ``id`` | |
174 | is the SHA1 identifier of the commit | |
175 | ||
176 | Returns | |
177 | GitPython.Commit | |
178 | """ | |
179 | options = {'max_count': 1} | |
180 | ||
181 | commits = Commit.find_all(self, id, **options) | |
182 | ||
183 | if not commits: | |
184 | raise ValueError, 'Invalid identifier %s' % id | |
185 | return commits[0] | |
186 | ||
187 | def commit_deltas_from(self, other_repo, ref = 'master', other_ref = 'master'): | |
188 | """ | |
189 | Returns a list of commits that is in ``other_repo`` but not in self | |
190 | ||
191 | Returns | |
192 | ``GitPython.Commit[]`` | |
193 | """ | |
194 | repo_refs = self.git.rev_list(ref).strip().splitlines() | |
195 | other_repo_refs = other_repo.git.rev_list(other_ref).strip().splitlines() | |
196 | ||
197 | diff_refs = list(set(other_repo_refs) - set(repo_refs)) | |
198 | return map(lambda ref: Commit.find_all(other_repo, ref, **{'max_count': 1}[0]), diff_refs) | |
199 | ||
200 | def tree(self, treeish = 'master', paths = []): | |
201 | """ | |
202 | The Tree object for the given treeish reference | |
203 | ||
204 | ``treeish`` | |
205 | is the reference (default 'master') | |
206 | ``paths`` | |
207 | is an optional Array of directory paths to restrict the tree (default []) | |
208 | ||
209 | Examples:: | |
210 | ||
211 | repo.tree('master', ['lib/']) | |
212 | ||
213 | ||
214 | Returns | |
215 | ``GitPython.Tree`` | |
216 | """ | |
217 | return Tree.construct(self, treeish, paths) | |
218 | ||
219 | def blob(self, id): | |
220 | """ | |
221 | The Blob object for the given id | |
222 | ||
223 | ``id`` | |
224 | is the SHA1 id of the blob | |
225 | ||
226 | Returns | |
227 | ``GitPython.Blob`` | |
228 | """ | |
229 | return Blob(self, **{'id': id}) | |
230 | ||
231 | def log(self, commit = 'master', path = None, **kwargs): | |
232 | """ | |
233 | The commit log for a treeish | |
234 | ||
235 | Returns | |
236 | ``GitPython.Commit[]`` | |
237 | """ | |
238 | options = {'pretty': 'raw'} | |
239 | options.update(kwargs) | |
240 | if path: | |
241 | arg = [commit, '--', path] | |
242 | else: | |
243 | arg = [commit] | |
244 | commits = self.git.log(*arg, **options) | |
245 | return Commit.list_from_string(self, commits) | |
246 | ||
247 | def diff(self, a, b, *paths): | |
248 | """ | |
249 | The diff from commit ``a`` to commit ``b``, optionally restricted to the given file(s) | |
250 | ||
251 | ``a`` | |
252 | is the base commit | |
253 | ``b`` | |
254 | is the other commit | |
255 | ||
256 | ``paths`` | |
257 | is an optional list of file paths on which to restrict the diff | |
258 | """ | |
259 | return self.git.diff(a, b, '--', *paths) | |
260 | ||
261 | def commit_diff(self, commit): | |
262 | """ | |
263 | The commit diff for the given commit | |
264 | ``commit`` is the commit name/id | |
265 | ||
266 | Returns | |
267 | ``GitPython.Diff[]`` | |
268 | """ | |
269 | return Commit.diff(self, commit) | |
270 | ||
271 | @classmethod | |
272 | def init_bare(self, path, mkdir=True, **kwargs): | |
273 | """ | |
274 | Initialize a bare git repository at the given path | |
275 | ||
276 | ``path`` | |
277 | is the full path to the repo (traditionally ends with /<name>.git) | |
278 | ||
279 | ``mkdir`` | |
280 | if specified will create the repository directory if it doesn't | |
281 | already exists. Creates the directory with a mode=0755. | |
282 | ||
283 | ``kwargs`` | |
284 | is any additional options to the git init command | |
285 | ||
286 | Examples:: | |
287 | ||
288 | GitPython.Repo.init_bare('/var/git/myrepo.git') | |
289 | ||
290 | Returns | |
291 | ``GitPython.Repo`` (the newly created repo) | |
292 | """ | |
293 | ||
294 | if mkdir and not os.path.exists(path): | |
295 | os.makedirs(path, 0755) | |
296 | ||
297 | git = Git(path) | |
298 | output = git.init(**kwargs) | |
299 | return Repo(path) | |
300 | create = init_bare | |
301 | ||
302 | def fork_bare(self, path, **kwargs): | |
303 | """ | |
304 | Fork a bare git repository from this repo | |
305 | ||
306 | ``path`` | |
307 | is the full path of the new repo (traditionally ends with /<name>.git) | |
308 | ||
309 | ``options`` | |
310 | is any additional options to the git clone command | |
311 | ||
312 | Returns | |
313 | ``GitPython.Repo`` (the newly forked repo) | |
314 | """ | |
315 | options = {'bare': True} | |
316 | options.update(kwargs) | |
317 | self.git.clone(self.path, path, **options) | |
318 | return Repo(path) | |
319 | ||
320 | def archive_tar(self, treeish = 'master', prefix = None): | |
321 | """ | |
322 | Archive the given treeish | |
323 | ||
324 | ``treeish`` | |
325 | is the treeish name/id (default 'master') | |
326 | ||
327 | ``prefix`` | |
328 | is the optional prefix | |
329 | ||
330 | Examples:: | |
331 | ||
332 | >>> repo.archive_tar | |
333 | <String containing tar archive> | |
334 | ||
335 | >>> repo.archive_tar('a87ff14') | |
336 | <String containing tar archive for commit a87ff14> | |
337 | ||
338 | >>> repo.archive_tar('master', 'myproject/') | |
339 | <String containing tar archive and prefixed with 'myproject/'> | |
340 | ||
341 | Returns | |
342 | str (containing tar archive) | |
343 | """ | |
344 | options = {} | |
345 | if prefix: | |
346 | options['prefix'] = prefix | |
347 | return self.git.archive(treeish, **options) | |
348 | ||
349 | def archive_tar_gz(self, treeish = 'master', prefix = None): | |
350 | """ | |
351 | Archive and gzip the given treeish | |
352 | ||
353 | ``treeish`` | |
354 | is the treeish name/id (default 'master') | |
355 | ||
356 | ``prefix`` | |
357 | is the optional prefix | |
358 | ||
359 | Examples:: | |
360 | ||
361 | >>> repo.archive_tar_gz | |
362 | <String containing tar.gz archive> | |
363 | ||
364 | >>> repo.archive_tar_gz('a87ff14') | |
365 | <String containing tar.gz archive for commit a87ff14> | |
366 | ||
367 | >>> repo.archive_tar_gz('master', 'myproject/') | |
368 | <String containing tar.gz archive and prefixed with 'myproject/'> | |
369 | ||
370 | Returns | |
371 | str (containing tar.gz archive) | |
372 | """ | |
373 | kwargs = {} | |
374 | if prefix: | |
375 | kwargs['prefix'] = prefix | |
376 | self.git.archive(treeish, "| gzip", **kwargs) | |
377 | ||
378 | def enable_daemon_serve(self): | |
379 | """ | |
380 | Enable git-daemon serving of this repository by writing the | |
381 | git-daemon-export-ok file to its git directory | |
382 | ||
383 | Returns | |
384 | None | |
385 | """ | |
386 | touch(os.path.join(self.path, DAEMON_EXPORT_FILE)) | |
387 | ||
388 | def disable_daemon_serve(self): | |
389 | """ | |
390 | Disable git-daemon serving of this repository by ensuring there is no | |
391 | git-daemon-export-ok file in its git directory | |
392 | ||
393 | Returns | |
394 | None | |
395 | """ | |
396 | return os.remove(os.path.join(self.path, DAEMON_EXPORT_FILE)) | |
397 | ||
398 | def _get_alternates(self): | |
399 | """ | |
400 | The list of alternates for this repo | |
401 | ||
402 | Returns | |
403 | list[str] (pathnames of alternates) | |
404 | """ | |
405 | alternates_path = os.path.join(self.path, *['objects', 'info', 'alternates']) | |
406 | ||
407 | if os.path.exists(alternates_path): | |
408 | try: | |
409 | f = open(alternates_path) | |
410 | alts = f.read() | |
411 | finally: | |
412 | f.close() | |
413 | return alts.strip().splitlines() | |
414 | else: | |
415 | return [] | |
416 | ||
417 | def _set_alternates(self, alts): | |
418 | """ | |
419 | Sets the alternates | |
420 | ||
421 | ``alts`` | |
422 | is the Array of String paths representing the alternates | |
423 | ||
424 | Returns | |
425 | None | |
426 | """ | |
427 | for alt in alts: | |
428 | if not os.path.exists(alt): | |
429 | raise NoSuchPathError("Could not set alternates. Alternate path %s must exist" % alt) | |
430 | ||
431 | if not alts: | |
432 | os.remove(os.path.join(self.path, *['objects', 'info', 'alternates'])) | |
433 | else: | |
434 | try: | |
435 | f = open(os.path.join(self.path, *['objects', 'info', 'alternates']), 'w') | |
436 | f.write("\n".join(alts)) | |
437 | finally: | |
438 | f.close() | |
439 | ||
440 | alternates = property(_get_alternates, _set_alternates) | |
441 | ||
442 | def __repr__(self): | |
443 | return '<GitPython.Repo "%s">' % self.path |
0 | # stats.py | |
1 | # Copyright (C) 2008 Michael Trier (mtrier@gmail.com) and contributors | |
2 | # | |
3 | # This module is part of GitPython and is released under | |
4 | # the BSD License: http://www.opensource.org/licenses/bsd-license.php | |
5 | ||
6 | class Stats(object): | |
7 | def __init__(self, repo, total, files): | |
8 | self.repo = repo | |
9 | self.total = total | |
10 | self.files = files | |
11 | ||
12 | @classmethod | |
13 | def list_from_string(cls, repo, text): | |
14 | hsh = {'total': {'insertions': 0, 'deletions': 0, 'lines': 0, 'files': 0}, 'files': {}} | |
15 | for line in text.splitlines(): | |
16 | (raw_insertions, raw_deletions, filename) = line.split("\t") | |
17 | insertions = raw_insertions != '-' and int(raw_insertions) or 0 | |
18 | deletions = raw_deletions != '-' and int(raw_deletions) or 0 | |
19 | hsh['total']['insertions'] += insertions | |
20 | hsh['total']['deletions'] += deletions | |
21 | hsh['total']['lines'] += insertions + deletions | |
22 | hsh['total']['files'] += 1 | |
23 | hsh['files'][filename.strip()] = {'insertions': insertions, | |
24 | 'deletions': deletions, | |
25 | 'lines': insertions + deletions} | |
26 | return Stats(repo, hsh['total'], hsh['files']) |
0 | # tag.py | |
1 | # Copyright (C) 2008 Michael Trier (mtrier@gmail.com) and contributors | |
2 | # | |
3 | # This module is part of GitPython and is released under | |
4 | # the BSD License: http://www.opensource.org/licenses/bsd-license.php | |
5 | ||
6 | from commit import Commit | |
7 | ||
8 | class Tag(object): | |
9 | def __init__(self, name, commit): | |
10 | """ | |
11 | Instantiate a new Tag | |
12 | ||
13 | ``name`` | |
14 | is the name of the head | |
15 | ||
16 | ``commit`` | |
17 | is the Commit that the head points to | |
18 | ||
19 | Returns | |
20 | ``GitPython.Tag`` | |
21 | """ | |
22 | self.name = name | |
23 | self.commit = commit | |
24 | ||
25 | @classmethod | |
26 | def find_all(cls, repo, **kwargs): | |
27 | """ | |
28 | Find all Tags | |
29 | ||
30 | ``repo`` | |
31 | is the Repo | |
32 | ||
33 | ``kwargs`` | |
34 | is a dict of options | |
35 | ||
36 | Returns | |
37 | ``GitPython.Tag[]`` | |
38 | """ | |
39 | options = {'sort': "committerdate", | |
40 | 'format': "%(refname)%00%(objectname)"} | |
41 | options.update(**kwargs) | |
42 | ||
43 | output = repo.git.for_each_ref("refs/tags", **options) | |
44 | return cls.list_from_string(repo, output) | |
45 | ||
46 | @classmethod | |
47 | def list_from_string(cls, repo, text): | |
48 | """ | |
49 | Parse out tag information into an array of baked Tag objects | |
50 | ||
51 | ``repo`` | |
52 | is the Repo | |
53 | ||
54 | ``text`` | |
55 | is the text output from the git command | |
56 | ||
57 | Returns | |
58 | ``GitPython.Tag[]`` | |
59 | """ | |
60 | tags = [] | |
61 | for line in text.splitlines(): | |
62 | tags.append(cls.from_string(repo, line)) | |
63 | return tags | |
64 | ||
65 | @classmethod | |
66 | def from_string(cls, repo, line): | |
67 | """ | |
68 | Create a new Tag instance from the given string. | |
69 | ||
70 | ``repo`` | |
71 | is the Repo | |
72 | ||
73 | ``line`` | |
74 | is the formatted tag information | |
75 | ||
76 | Format | |
77 | name: [a-zA-Z_/]+ | |
78 | <null byte> | |
79 | id: [0-9A-Fa-f]{40} | |
80 | ||
81 | Returns | |
82 | ``GitPython.Tag`` | |
83 | """ | |
84 | full_name, ids = line.split("\x00") | |
85 | name = full_name.split("/")[-1] | |
86 | commit = Commit(repo, **{'id': ids}) | |
87 | return Tag(name, commit) | |
88 | ||
89 | def __repr__(self): | |
90 | return '<GitPython.Tag "%s">' % self.name |
0 | # tree.py | |
1 | # Copyright (C) 2008 Michael Trier (mtrier@gmail.com) and contributors | |
2 | # | |
3 | # This module is part of GitPython and is released under | |
4 | # the BSD License: http://www.opensource.org/licenses/bsd-license.php | |
5 | ||
6 | import os | |
7 | from lazy import LazyMixin | |
8 | import blob | |
9 | ||
10 | class Tree(LazyMixin): | |
11 | def __init__(self, repo, **kwargs): | |
12 | LazyMixin.__init__(self) | |
13 | self.repo = repo | |
14 | self.id = None | |
15 | self.mode = None | |
16 | self.name = None | |
17 | self.contents = None | |
18 | ||
19 | for k, v in kwargs.items(): | |
20 | setattr(self, k, v) | |
21 | ||
22 | def __bake__(self): | |
23 | temp = Tree.construct(self.repo, self.id) | |
24 | self.contents = temp.contents | |
25 | ||
26 | @classmethod | |
27 | def construct(cls, repo, treeish, paths = []): | |
28 | output = repo.git.ls_tree(treeish, *paths) | |
29 | return Tree(repo, **{'id': treeish}).construct_initialize(repo, treeish, output) | |
30 | ||
31 | def construct_initialize(self, repo, id, text): | |
32 | self.repo = repo | |
33 | self.id = id | |
34 | self.contents = [] | |
35 | self.__baked__ = False | |
36 | ||
37 | for line in text.splitlines(): | |
38 | self.contents.append(self.content_from_string(self.repo, line)) | |
39 | ||
40 | self.contents = [c for c in self.contents if c is not None] | |
41 | ||
42 | self.__bake_it__() | |
43 | return self | |
44 | ||
45 | def content_from_string(self, repo, text): | |
46 | """ | |
47 | Parse a content item and create the appropriate object | |
48 | ||
49 | ``repo`` | |
50 | is the Repo | |
51 | ||
52 | ``text`` | |
53 | is the single line containing the items data in `git ls-tree` format | |
54 | ||
55 | Returns | |
56 | ``GitPython.Blob`` or ``GitPython.Tree`` | |
57 | """ | |
58 | try: | |
59 | mode, typ, id, name = text.expandtabs(1).split(" ", 4) | |
60 | except: | |
61 | return None | |
62 | ||
63 | if typ == "tree": | |
64 | return Tree(repo, **{'id': id, 'mode': mode, 'name': name}) | |
65 | elif typ == "blob": | |
66 | return blob.Blob(repo, **{'id': id, 'mode': mode, 'name': name}) | |
67 | elif typ == "commit": | |
68 | return None | |
69 | else: | |
70 | raise(TypeError, "Invalid type: %s" % typ) | |
71 | ||
72 | def __div__(self, file): | |
73 | """ | |
74 | Find the named object in this tree's contents | |
75 | ||
76 | Examples:: | |
77 | ||
78 | >>> Repo('/path/to/python-git').tree/'lib' | |
79 | <GitPython.Tree "6cc23ee138be09ff8c28b07162720018b244e95e"> | |
80 | >>> Repo('/path/to/python-git').tree/'README.txt' | |
81 | <GitPython.Blob "8b1e02c0fb554eed2ce2ef737a68bb369d7527df"> | |
82 | ||
83 | Returns | |
84 | ``GitPython.Blob`` or ``GitPython.Tree`` or ``None`` if not found | |
85 | """ | |
86 | contents = [c for c in self.contents if c.name == file] | |
87 | return contents and contents[0] or None | |
88 | ||
89 | @property | |
90 | def basename(self): | |
91 | os.path.basename(self.name) | |
92 | ||
93 | def __repr__(self): | |
94 | return '<GitPython.Tree "%s">' % self.id |
0 | # utils.py | |
1 | # Copyright (C) 2008 Michael Trier (mtrier@gmail.com) and contributors | |
2 | # | |
3 | # This module is part of GitPython and is released under | |
4 | # the BSD License: http://www.opensource.org/licenses/bsd-license.php | |
5 | ||
6 | import os | |
7 | ||
8 | def dashify(string): | |
9 | return string.replace('_', '-') | |
10 | ||
11 | def touch(filename): | |
12 | os.utime(filename) | |
13 | ||
14 | def is_git_dir(d): | |
15 | """ This is taken from the git setup.c:is_git_directory | |
16 | function.""" | |
17 | ||
18 | if os.path.isdir(d) and \ | |
19 | os.path.isdir(os.path.join(d, 'objects')) and \ | |
20 | os.path.isdir(os.path.join(d, 'refs')): | |
21 | headref = os.path.join(d, 'HEAD') | |
22 | return os.path.isfile(headref) or \ | |
23 | (os.path.islink(headref) and | |
24 | os.readlink(headref).startswith('refs')) | |
25 | return False |
0 | from ez_setup import use_setuptools | |
1 | use_setuptools() | |
2 | from setuptools import setup, find_packages | |
3 | from distutils.command.build_py import build_py as _build_py | |
4 | from setuptools.command.sdist import sdist as _sdist | |
5 | import os | |
6 | from os import path | |
7 | ||
8 | v = open(path.join(path.dirname(__file__), 'VERSION')) | |
9 | VERSION = v.readline().strip() | |
10 | v.close() | |
11 | ||
12 | class build_py(_build_py): | |
13 | def run(self): | |
14 | init = path.join(self.build_lib, 'git', '__init__.py') | |
15 | if path.exists(init): | |
16 | os.unlink(init) | |
17 | _build_py.run(self) | |
18 | _stamp_version(init) | |
19 | self.byte_compile([init]) | |
20 | ||
21 | class sdist(_sdist): | |
22 | def make_release_tree (self, base_dir, files): | |
23 | _sdist.make_release_tree(self, base_dir, files) | |
24 | orig = path.join('lib', 'git', '__init__.py') | |
25 | assert path.exists(orig) | |
26 | dest = path.join(base_dir, orig) | |
27 | if hasattr(os, 'link') and path.exists(dest): | |
28 | os.unlink(dest) | |
29 | self.copy_file(orig, dest) | |
30 | _stamp_version(dest) | |
31 | ||
32 | def _stamp_version(filename): | |
33 | found, out = False, [] | |
34 | f = open(filename, 'r') | |
35 | for line in f: | |
36 | if '__version__ =' in line: | |
37 | line = line.replace("'git'", "'%s'" % VERSION) | |
38 | found = True | |
39 | out.append(line) | |
40 | f.close() | |
41 | ||
42 | if found: | |
43 | f = open(filename, 'w') | |
44 | f.writelines(out) | |
45 | f.close() | |
46 | ||
47 | ||
48 | setup(name = "GitPython", | |
49 | cmdclass={'build_py': build_py, 'sdist': sdist}, | |
50 | version = VERSION, | |
51 | description = "Python Git Library", | |
52 | author = "Michael Trier", | |
53 | author_email = "mtrier@gmail.com", | |
54 | url = "http://gitorious.org/projects/git-python/", | |
55 | packages = find_packages('lib'), | |
56 | package_dir = {'':'lib'}, | |
57 | license = "BSD License", | |
58 | long_description = """\ | |
59 | GitPython is a python library used to interact with Git repositories. | |
60 | ||
61 | GitPython provides object model access to your git repository. Once you have | |
62 | created a repository object, you can traverse it to find parent commit(s), | |
63 | trees, blobs, etc. | |
64 | ||
65 | GitPython is a port of the grit library in Ruby created by | |
66 | Tom Preston-Werner and Chris Wanstrath. | |
67 | """, | |
68 | classifiers = [ | |
69 | "Development Status :: 3 - Alpha", | |
70 | "Intended Audience :: Developers", | |
71 | "License :: OSI Approved :: BSD License", | |
72 | "Programming Language :: Python", | |
73 | "Topic :: Software Development :: Libraries :: Python Modules", | |
74 | ] | |
75 | ) |