From 919164df96d9f956c8be712f33a9a037b097745b Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 16 Oct 2009 13:05:01 +0200 Subject: repo.untracked_files added including test --- lib/git/repo.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) (limited to 'lib/git') diff --git a/lib/git/repo.py b/lib/git/repo.py index 554c10cb..6edb7f62 100644 --- a/lib/git/repo.py +++ b/lib/git/repo.py @@ -285,6 +285,37 @@ class Repo(object): return False return len(self.git.diff('HEAD', '--').strip()) > 0 + + @property + def untracked_files(self): + """ + Returns + list(str,...) + + Files currently untracked as they have not been staged yet. Paths + are relative to the current working directory of the git command. + + Note + ignored files will not appear here, i.e. files mentioned in .gitignore + """ + # make sure we get all files, no only untracked directores + proc = self.git.commit(untracked_files=True, as_process=True) + stream = iter(proc.stdout) + untracked_files = list() + for line in stream: + if not line.startswith("# Untracked files:"): + continue + # skip two lines + stream.next() + stream.next() + + for untracked_info in stream: + if not untracked_info.startswith("#\t"): + break + untracked_files.append(untracked_info.replace("#\t", "").rstrip()) + # END for each utracked info line + # END for each line + return untracked_files @property def active_branch(self): -- cgit v1.2.1 From bb24f67e64b4ebe11c4d3ce7df021a6ad7ca98f2 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 16 Oct 2009 16:09:07 +0200 Subject: Fixed object bug that would cause object ids not to be resolved to sha's as this was assumed - now there is a test for it as well repo: removed diff and commit_diff methods, added 'head' property returning the current head as Reference object --- lib/git/objects/base.py | 32 ++++++++++++++++++++------------ lib/git/refs.py | 2 +- lib/git/repo.py | 38 ++++++++++---------------------------- 3 files changed, 31 insertions(+), 41 deletions(-) (limited to 'lib/git') diff --git a/lib/git/objects/base.py b/lib/git/objects/base.py index 3b48e066..d780c7b3 100644 --- a/lib/git/objects/base.py +++ b/lib/git/objects/base.py @@ -15,22 +15,12 @@ class Object(LazyMixin): This Object also serves as a constructor for instances of the correct type:: - inst = Object(repo,id) + inst = Object.new(repo,id) """ TYPES = ("blob", "tree", "commit", "tag") __slots__ = ("repo", "id", "size", "data" ) type = None # to be set by subclass - def __new__(cls, repo, id, *args, **kwargs): - if cls is Object: - hexsha, typename, size = repo.git.get_object_header(id) - obj_type = utils.get_object_type_by_name(typename) - inst = super(Object,cls).__new__(obj_type, repo, hexsha, *args, **kwargs) - inst.size = size - return inst - else: - return super(Object,cls).__new__(cls, repo, id, *args, **kwargs) - def __init__(self, repo, id): """ Initialize an object by identifying it by its id. All keyword arguments @@ -45,7 +35,25 @@ class Object(LazyMixin): super(Object,self).__init__() self.repo = repo self.id = id - + + @classmethod + def new(cls, repo, id): + """ + Return + New Object instance of a type appropriate to the object type behind + id. The id of the newly created object will be a hexsha even though + the input id may have been a Reference or Rev-Spec + + Note + This cannot be a __new__ method as it would always call __init__ + with the input id which is not necessarily a hexsha. + """ + hexsha, typename, size = repo.git.get_object_header(id) + obj_type = utils.get_object_type_by_name(typename) + inst = obj_type(repo, hexsha) + inst.size = size + return inst + def _set_self_from_args_(self, args_dict): """ Initialize attributes on self from the given dict that was retrieved diff --git a/lib/git/refs.py b/lib/git/refs.py index a4d7bbb1..4445f252 100644 --- a/lib/git/refs.py +++ b/lib/git/refs.py @@ -72,7 +72,7 @@ class Reference(LazyMixin, Iterable): """ # have to be dynamic here as we may be a tag which can point to anything # Our path will be resolved to the hexsha which will be used accordingly - return Object(self.repo, self.path) + return Object.new(self.repo, self.path) @classmethod def iter_items(cls, repo, common_path = "refs", **kwargs): diff --git a/lib/git/repo.py b/lib/git/repo.py index 6edb7f62..a77ced3c 100644 --- a/lib/git/repo.py +++ b/lib/git/repo.py @@ -106,6 +106,15 @@ class Repo(object): # alias heads branches = heads + @property + def head(self): + """ + Return + Head Object, reference pointing to the current head of the repository + """ + return Head(self,'HEAD') + + @property def tags(self): """ @@ -129,7 +138,7 @@ class Repo(object): if rev is None: rev = self.active_branch - c = Object(self, rev) + c = Object.new(self, rev) assert c.type == "commit", "Revision %s did not point to a commit, but to %s" % (rev, c) return c @@ -327,34 +336,7 @@ class Repo(object): """ return Head( self, self.git.symbolic_ref('HEAD').strip() ) - - def diff(self, a, b, *paths): - """ - The diff from commit ``a`` to commit ``b``, optionally restricted to the given file(s) - - ``a`` - is the base commit - ``b`` - is the other commit - - ``paths`` - is an optional list of file paths on which to restrict the diff - Returns - ``str`` - """ - return self.git.diff(a, b, '--', *paths) - - def commit_diff(self, commit): - """ - The commit diff for the given commit - ``commit`` is the commit name/id - - Returns - ``git.Diff[]`` - """ - return Commit.diff(self, commit) - def blame(self, rev, file): """ The blame information for the given file at the given revision. -- cgit v1.2.1 From b372e26366348920eae32ee81a47b469b511a21f Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Fri, 16 Oct 2009 19:19:57 +0200 Subject: added Diffable interface to objects.base, its used by Commit and Tree objects. Diff class has been prepared to process raw input, but its not yet more than a frame --- lib/git/diff.py | 24 ++++++++++++---- lib/git/objects/base.py | 71 +++++++++++++++++++++++++++++++++++++++++++++++ lib/git/objects/commit.py | 56 +------------------------------------ lib/git/objects/tree.py | 3 +- 4 files changed, 92 insertions(+), 62 deletions(-) (limited to 'lib/git') diff --git a/lib/git/diff.py b/lib/git/diff.py index 0db83b4f..e16d5b07 100644 --- a/lib/git/diff.py +++ b/lib/git/diff.py @@ -9,7 +9,7 @@ import objects.blob as blob class Diff(object): """ - A Diff contains diff information between two commits. + A Diff contains diff information between two Trees. It contains two sides a and b of the diff, members are prefixed with "a" and "b" respectively to inidcate that. @@ -74,18 +74,20 @@ class Diff(object): self.diff = diff @classmethod - def _list_from_string(cls, repo, text): + def _index_from_patch_format(cls, repo, stream): """ - Create a new diff object from the given text + Create a new DiffIndex from the given text which must be in patch format ``repo`` is the repository we are operating on - it is required - ``text`` - result of 'git diff' between two commits or one commit and the index + ``stream`` + result of 'git diff' as a stream (supporting file protocol) Returns - git.Diff[] + git.DiffIndex """ + # for now, we have to bake the stream + text = stream.read() diffs = [] diff_header = cls.re_header.match @@ -102,4 +104,14 @@ class Diff(object): new_file, deleted_file, rename_from, rename_to, diff[header.end():])) return diffs + + @classmethod + def _index_from_raw_format(cls, repo, stream): + """ + Create a new DiffIndex from the given stream which must be in raw format. + + Returns + git.DiffIndex + """ + raise NotImplementedError("") diff --git a/lib/git/objects/base.py b/lib/git/objects/base.py index d780c7b3..1bb2e8f1 100644 --- a/lib/git/objects/base.py +++ b/lib/git/objects/base.py @@ -172,3 +172,74 @@ class IndexObject(Object): return mode +class Diffable(object): + """ + Common interface for all object that can be diffed against another object of compatible type. + + NOTE: + Subclasses require a repo member as it is the case for Object instances, for practical + reasons we do not derive from Object. + """ + __slots__ = tuple() + + # subclasses provide additional arguments to the git-diff comamnd by supplynig + # them in this tuple + _diff_args = tuple() + + def diff(self, other=None, paths=None, create_patch=False, **kwargs): + """ + Creates diffs between two items being trees, trees and index or an + index and the working tree. + + ``other`` + Is the item to compare us with. + If None, we will be compared to the working tree. + + ``paths`` + is a list of paths or a single path to limit the diff to. + It will only include at least one of the givne path or paths. + + ``create_patch`` + If True, the returned Diff contains a detailed patch that if applied + makes the self to other. Patches are somwhat costly as blobs have to be read + and diffed. + + ``kwargs`` + Additional arguments passed to git-diff, such as + R=True to swap both sides of the diff. + + Returns + git.DiffIndex + + Note + Rename detection will only work if create_patch is True + """ + args = list(self._diff_args[:]) + args.append( "--abbrev=40" ) # we need full shas + args.append( "--full-index" ) # get full index paths, not only filenames + + if create_patch: + args.append("-p") + args.append("-M") # check for renames + else: + args.append("--raw") + + paths = paths or [] + if paths: + paths.insert(0, "--") + + if other is not None: + args.insert(0, other) + + args.insert(0,self) + args.extend(paths) + + kwargs['as_process'] = True + proc = self.repo.git.diff(*args, **kwargs) + + diff_method = diff.Diff._index_from_raw_format + if create_patch: + diff_method = diff.Diff._index_from_patch_format(self.repo, proc.stdout) + return diff_method(self.repo, proc.stdout) + + diff --git a/lib/git/objects/commit.py b/lib/git/objects/commit.py index 847f4dec..7ed38703 100644 --- a/lib/git/objects/commit.py +++ b/lib/git/objects/commit.py @@ -11,7 +11,7 @@ from tree import Tree import base import utils -class Commit(base.Object, Iterable): +class Commit(base.Object, Iterable, base.Diffable): """ Wraps a git Commit object. @@ -176,60 +176,6 @@ class Commit(base.Object, Iterable): return self.iter_items( self.repo, self, paths, **kwargs ) - @classmethod - def diff(cls, repo, a, b=None, paths=None): - """ - Creates diffs between a tree and the index or between two trees: - - ``repo`` - is the Repo - - ``a`` - is a named commit - - ``b`` - is an optional named commit. Passing a list assumes you - wish to omit the second named commit and limit the diff to the - given paths. - - ``paths`` - is a list of paths to limit the diff to. - - Returns - git.Diff[]:: - - between tree and the index if only a is given - between two trees if a and b are given and are commits - """ - paths = paths or [] - - if isinstance(b, list): - paths = b - b = None - - if paths: - paths.insert(0, "--") - - if b: - paths.insert(0, b) - paths.insert(0, a) - text = repo.git.diff('-M', full_index=True, *paths) - return diff.Diff._list_from_string(repo, text) - - @property - def diffs(self): - """ - Returns - git.Diff[] - Diffs between this commit and its first parent or all changes if this - commit is the first commit and has no parent. - """ - if not self.parents: - d = self.repo.git.show(self.id, '-M', full_index=True, pretty='raw') - return diff.Diff._list_from_string(self.repo, d) - else: - return self.diff(self.repo, self.parents[0].id, self.id) - @property def stats(self): """ diff --git a/lib/git/objects/tree.py b/lib/git/objects/tree.py index abfa9622..4d3e9ebd 100644 --- a/lib/git/objects/tree.py +++ b/lib/git/objects/tree.py @@ -15,7 +15,7 @@ def sha_to_hex(sha): assert len(hexsha) == 40, "Incorrect length of sha1 string: %d" % hexsha return hexsha -class Tree(base.IndexObject): +class Tree(base.IndexObject, base.Diffable): """ Tress represent a ordered list of Blobs and other Trees. Hence it can be accessed like a list. @@ -169,6 +169,7 @@ class Tree(base.IndexObject): def traverse(self, max_depth=-1, predicate = lambda i: True): """ Returns + Iterator to traverse the tree recursively up to the given level. The iterator returns Blob and Tree objects -- cgit v1.2.1 From a5cf1bc1d3e38ab32a20707d66b08f1bb0beae91 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sat, 17 Oct 2009 20:13:02 +0200 Subject: Removed a few diff-related test cases that fail now as the respective method is missing - these tests have to be redone in test-diff module accordingly --- lib/git/objects/commit.py | 1 + 1 file changed, 1 insertion(+) (limited to 'lib/git') diff --git a/lib/git/objects/commit.py b/lib/git/objects/commit.py index 7ed38703..521130c5 100644 --- a/lib/git/objects/commit.py +++ b/lib/git/objects/commit.py @@ -214,6 +214,7 @@ class Commit(base.Object, Iterable, base.Diffable): if not hasattr(stream,'next'): stream = proc_or_stream.stdout + for line in stream: id = line.split()[1] assert line.split()[0] == "commit" -- cgit v1.2.1 From 9946e0ce07c8d93a43bd7b8900ddf5d913fe3b03 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 18 Oct 2009 12:33:06 +0200 Subject: implemented diff tests, but will have to move the diff module as it needs to create objects, whose import would create a dependency cycle --- lib/git/objects/base.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'lib/git') diff --git a/lib/git/objects/base.py b/lib/git/objects/base.py index 1bb2e8f1..b347b5f1 100644 --- a/lib/git/objects/base.py +++ b/lib/git/objects/base.py @@ -214,6 +214,9 @@ class Diffable(object): Note Rename detection will only work if create_patch is True """ + # import it in a retared fashion to avoid dependency cycle + from git.diff import Diff + args = list(self._diff_args[:]) args.append( "--abbrev=40" ) # we need full shas args.append( "--full-index" ) # get full index paths, not only filenames @@ -237,9 +240,9 @@ class Diffable(object): kwargs['as_process'] = True proc = self.repo.git.diff(*args, **kwargs) - diff_method = diff.Diff._index_from_raw_format + diff_method = Diff._index_from_raw_format if create_patch: - diff_method = diff.Diff._index_from_patch_format(self.repo, proc.stdout) + diff_method = Diff._index_from_patch_format return diff_method(self.repo, proc.stdout) -- cgit v1.2.1 From 17852bde65c12c80170d4c67b3136d8e958a92a9 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 18 Oct 2009 12:50:11 +0200 Subject: repo: fixed untracked files function which used git-commit before, it can open vim to get a message though which makes the program appear to freeze - using git-status now --- lib/git/repo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/git') diff --git a/lib/git/repo.py b/lib/git/repo.py index a77ced3c..cc4a6c6b 100644 --- a/lib/git/repo.py +++ b/lib/git/repo.py @@ -308,7 +308,7 @@ class Repo(object): ignored files will not appear here, i.e. files mentioned in .gitignore """ # make sure we get all files, no only untracked directores - proc = self.git.commit(untracked_files=True, as_process=True) + proc = self.git.status(untracked_files=True, as_process=True) stream = iter(proc.stdout) untracked_files = list() for line in stream: -- cgit v1.2.1 From aed099a73025422f0550f5dd5c3e4651049494b2 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 18 Oct 2009 12:54:16 +0200 Subject: resolved cyclic inclusion issue by moving the Diffable interface into the diff module, which probably is the right thing to do anyway --- lib/git/__init__.py | 2 +- lib/git/diff.py | 72 +++++++++++++++++++++++++++++++++++++++++++++ lib/git/objects/base.py | 75 ----------------------------------------------- lib/git/objects/commit.py | 2 +- lib/git/objects/tree.py | 3 +- 5 files changed, 76 insertions(+), 78 deletions(-) (limited to 'lib/git') diff --git a/lib/git/__init__.py b/lib/git/__init__.py index 6f482128..e2adac62 100644 --- a/lib/git/__init__.py +++ b/lib/git/__init__.py @@ -12,7 +12,7 @@ __version__ = 'git' from git.objects import * from git.refs import * from git.actor import Actor -from git.diff import Diff +from git.diff import * from git.errors import InvalidGitRepositoryError, NoSuchPathError, GitCommandError from git.cmd import Git from git.repo import Repo diff --git a/lib/git/diff.py b/lib/git/diff.py index e16d5b07..760897ec 100644 --- a/lib/git/diff.py +++ b/lib/git/diff.py @@ -7,6 +7,78 @@ import re import objects.blob as blob + +class Diffable(object): + """ + Common interface for all object that can be diffed against another object of compatible type. + + NOTE: + Subclasses require a repo member as it is the case for Object instances, for practical + reasons we do not derive from Object. + """ + __slots__ = tuple() + + # subclasses provide additional arguments to the git-diff comamnd by supplynig + # them in this tuple + _diff_args = tuple() + + def diff(self, other=None, paths=None, create_patch=False, **kwargs): + """ + Creates diffs between two items being trees, trees and index or an + index and the working tree. + + ``other`` + Is the item to compare us with. + If None, we will be compared to the working tree. + + ``paths`` + is a list of paths or a single path to limit the diff to. + It will only include at least one of the givne path or paths. + + ``create_patch`` + If True, the returned Diff contains a detailed patch that if applied + makes the self to other. Patches are somwhat costly as blobs have to be read + and diffed. + + ``kwargs`` + Additional arguments passed to git-diff, such as + R=True to swap both sides of the diff. + + Returns + git.DiffIndex + + Note + Rename detection will only work if create_patch is True + """ + args = list(self._diff_args[:]) + args.append( "--abbrev=40" ) # we need full shas + args.append( "--full-index" ) # get full index paths, not only filenames + + if create_patch: + args.append("-p") + args.append("-M") # check for renames + else: + args.append("--raw") + + paths = paths or [] + if paths: + paths.insert(0, "--") + + if other is not None: + args.insert(0, other) + + args.insert(0,self) + args.extend(paths) + + kwargs['as_process'] = True + proc = self.repo.git.diff(*args, **kwargs) + + diff_method = Diff._index_from_raw_format + if create_patch: + diff_method = Diff._index_from_patch_format + return diff_method(self.repo, proc.stdout) + + class Diff(object): """ A Diff contains diff information between two Trees. diff --git a/lib/git/objects/base.py b/lib/git/objects/base.py index b347b5f1..ab1da7b0 100644 --- a/lib/git/objects/base.py +++ b/lib/git/objects/base.py @@ -170,79 +170,4 @@ class IndexObject(Object): mode += int(char) << iteration*3 # END for each char return mode - -class Diffable(object): - """ - Common interface for all object that can be diffed against another object of compatible type. - - NOTE: - Subclasses require a repo member as it is the case for Object instances, for practical - reasons we do not derive from Object. - """ - __slots__ = tuple() - - # subclasses provide additional arguments to the git-diff comamnd by supplynig - # them in this tuple - _diff_args = tuple() - - def diff(self, other=None, paths=None, create_patch=False, **kwargs): - """ - Creates diffs between two items being trees, trees and index or an - index and the working tree. - - ``other`` - Is the item to compare us with. - If None, we will be compared to the working tree. - - ``paths`` - is a list of paths or a single path to limit the diff to. - It will only include at least one of the givne path or paths. - - ``create_patch`` - If True, the returned Diff contains a detailed patch that if applied - makes the self to other. Patches are somwhat costly as blobs have to be read - and diffed. - - ``kwargs`` - Additional arguments passed to git-diff, such as - R=True to swap both sides of the diff. - - Returns - git.DiffIndex - - Note - Rename detection will only work if create_patch is True - """ - # import it in a retared fashion to avoid dependency cycle - from git.diff import Diff - - args = list(self._diff_args[:]) - args.append( "--abbrev=40" ) # we need full shas - args.append( "--full-index" ) # get full index paths, not only filenames - - if create_patch: - args.append("-p") - args.append("-M") # check for renames - else: - args.append("--raw") - - paths = paths or [] - if paths: - paths.insert(0, "--") - - if other is not None: - args.insert(0, other) - - args.insert(0,self) - args.extend(paths) - - kwargs['as_process'] = True - proc = self.repo.git.diff(*args, **kwargs) - - diff_method = Diff._index_from_raw_format - if create_patch: - diff_method = Diff._index_from_patch_format - return diff_method(self.repo, proc.stdout) - - diff --git a/lib/git/objects/commit.py b/lib/git/objects/commit.py index 521130c5..181cbb52 100644 --- a/lib/git/objects/commit.py +++ b/lib/git/objects/commit.py @@ -11,7 +11,7 @@ from tree import Tree import base import utils -class Commit(base.Object, Iterable, base.Diffable): +class Commit(base.Object, Iterable, diff.Diffable): """ Wraps a git Commit object. diff --git a/lib/git/objects/tree.py b/lib/git/objects/tree.py index 4d3e9ebd..c35c075e 100644 --- a/lib/git/objects/tree.py +++ b/lib/git/objects/tree.py @@ -8,6 +8,7 @@ import os import blob import base import binascii +import git.diff as diff def sha_to_hex(sha): """Takes a string and returns the hex of the sha within""" @@ -15,7 +16,7 @@ def sha_to_hex(sha): assert len(hexsha) == 40, "Incorrect length of sha1 string: %d" % hexsha return hexsha -class Tree(base.IndexObject, base.Diffable): +class Tree(base.IndexObject, diff.Diffable): """ Tress represent a ordered list of Blobs and other Trees. Hence it can be accessed like a list. -- cgit v1.2.1 From e063d101face690b8cf4132fa419c5ce3857ef44 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 18 Oct 2009 13:24:04 +0200 Subject: diff: implemented raw diff parsing which appears to be able to handle possible input types, DiffIndex still requires implementation though --- lib/git/diff.py | 62 ++++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 53 insertions(+), 9 deletions(-) (limited to 'lib/git') diff --git a/lib/git/diff.py b/lib/git/diff.py index 760897ec..6a6a097c 100644 --- a/lib/git/diff.py +++ b/lib/git/diff.py @@ -60,15 +60,19 @@ class Diffable(object): else: args.append("--raw") - paths = paths or [] - if paths: - paths.insert(0, "--") + if paths is not None and not isinstance(paths, (tuple,list)): + paths = [ paths ] if other is not None: args.insert(0, other) args.insert(0,self) - args.extend(paths) + + # paths is list here or None + if paths: + args.append("--") + args.extend(paths) + # END paths handling kwargs['as_process'] = True proc = self.repo.git.diff(*args, **kwargs) @@ -79,6 +83,16 @@ class Diffable(object): return diff_method(self.repo, proc.stdout) +class DiffIndex(list): + """ + Implements an Index for diffs, allowing a list of Diffs to be queried by + the diff properties. + + The class improves the diff handling convenience + """ + + + class Diff(object): """ A Diff contains diff information between two Trees. @@ -99,7 +113,7 @@ class Diff(object): ``Deleted File``:: b_mode is None - b_blob is NOne + b_blob is None """ # precompiled regex @@ -160,7 +174,7 @@ class Diff(object): """ # for now, we have to bake the stream text = stream.read() - diffs = [] + index = DiffIndex() diff_header = cls.re_header.match for diff in ('\n' + text).split('\ndiff --git')[1:]: @@ -171,19 +185,49 @@ class Diff(object): a_blob_id, b_blob_id, b_mode = header.groups() new_file, deleted_file = bool(new_file_mode), bool(deleted_file_mode) - diffs.append(Diff(repo, a_path, b_path, a_blob_id, b_blob_id, + index.append(Diff(repo, a_path, b_path, a_blob_id, b_blob_id, old_mode or deleted_file_mode, new_mode or new_file_mode or b_mode, new_file, deleted_file, rename_from, rename_to, diff[header.end():])) - return diffs + return index @classmethod def _index_from_raw_format(cls, repo, stream): """ Create a new DiffIndex from the given stream which must be in raw format. + NOTE: + This format is inherently incapable of detecting renames, hence we only + modify, delete and add files + Returns git.DiffIndex """ - raise NotImplementedError("") + # handles + # :100644 100644 6870991011cc8d9853a7a8a6f02061512c6a8190 37c5e30c879213e9ae83b21e9d11e55fc20c54b7 M .gitignore + index = DiffIndex() + for line in stream: + if not line.startswith(":"): + continue + # END its not a valid diff line + old_mode, new_mode, a_blob_id, b_blob_id, modification_id, path = line[1:].split() + a_path = path + b_path = path + deleted_file = False + new_file = False + if modification_id == 'D': + b_path = None + deleted_file = True + elif modification_id == 'A': + a_path = None + new_file = True + # END add/remove handling + + + diff = Diff(repo, a_path, b_path, a_blob_id, b_blob_id, old_mode, new_mode, + new_file, deleted_file, None, None, '') + index.append(diff) + # END for each line + + return index -- cgit v1.2.1 From 9acc7806d6bdb306a929c460437d3d03e5e48dcd Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 18 Oct 2009 14:24:30 +0200 Subject: DiffIndex implemented including test --- lib/git/diff.py | 65 +++++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 56 insertions(+), 9 deletions(-) (limited to 'lib/git') diff --git a/lib/git/diff.py b/lib/git/diff.py index 6a6a097c..1774597a 100644 --- a/lib/git/diff.py +++ b/lib/git/diff.py @@ -22,6 +22,10 @@ class Diffable(object): # them in this tuple _diff_args = tuple() + # Temporary standin for Index type until we have a real index type + class Index(object): + pass + def diff(self, other=None, paths=None, create_patch=False, **kwargs): """ Creates diffs between two items being trees, trees and index or an @@ -30,6 +34,7 @@ class Diffable(object): ``other`` Is the item to compare us with. If None, we will be compared to the working tree. + If Index ( type ), it will be compared against the index ``paths`` is a list of paths or a single path to limit the diff to. @@ -63,8 +68,10 @@ class Diffable(object): if paths is not None and not isinstance(paths, (tuple,list)): paths = [ paths ] - if other is not None: + if other is not None and other is not self.Index: args.insert(0, other) + if other is self.Index: + args.insert(0, "--cached") args.insert(0,self) @@ -90,7 +97,33 @@ class DiffIndex(list): The class improves the diff handling convenience """ + # change type invariant identifying possible ways a blob can have changed + # A = Added + # D = Deleted + # R = Renamed + # NOTE: 'Modified' mode is impllied as it wouldn't be listed as a diff otherwise + change_type = ("A", "D", "R") + + def iter_change_type(self, change_type): + """ + Return + iterator yieling Diff instances that match the given change_type + + ``change_type`` + Member of DiffIndex.change_type + """ + if change_type not in self.change_type: + raise ValueError( "Invalid change type: %s" % change_type ) + + for diff in self: + if change_type == "A" and diff.new_file: + yield diff + elif change_type == "D" and diff.deleted_file: + yield diff + elif change_type == "R" and diff.renamed: + yield diff + # END for each diff class Diff(object): @@ -132,7 +165,7 @@ class Diff(object): """, re.VERBOSE | re.MULTILINE) re_is_null_hexsha = re.compile( r'^0{40}$' ) __slots__ = ("a_blob", "b_blob", "a_mode", "b_mode", "new_file", "deleted_file", - "rename_from", "rename_to", "renamed", "diff") + "rename_from", "rename_to", "diff") def __init__(self, repo, a_path, b_path, a_blob_id, b_blob_id, a_mode, b_mode, new_file, deleted_file, rename_from, @@ -148,17 +181,29 @@ class Diff(object): self.a_mode = a_mode self.b_mode = b_mode + if self.a_mode: self.a_mode = blob.Blob._mode_str_to_int( self.a_mode ) if self.b_mode: self.b_mode = blob.Blob._mode_str_to_int( self.b_mode ) + self.new_file = new_file self.deleted_file = deleted_file - self.rename_from = rename_from - self.rename_to = rename_to - self.renamed = rename_from != rename_to + + # be clear and use None instead of empty strings + self.rename_from = rename_from or None + self.rename_to = rename_to or None + self.diff = diff + @property + def renamed(self): + """ + Returns: + True if the blob of our diff has been renamed + """ + return self.rename_from != self.rename_to + @classmethod def _index_from_patch_format(cls, repo, stream): """ @@ -210,20 +255,22 @@ class Diff(object): if not line.startswith(":"): continue # END its not a valid diff line - old_mode, new_mode, a_blob_id, b_blob_id, modification_id, path = line[1:].split() + old_mode, new_mode, a_blob_id, b_blob_id, change_type, path = line[1:].split() a_path = path b_path = path deleted_file = False new_file = False - if modification_id == 'D': + + # NOTE: We cannot conclude from the existance of a blob to change type + # as diffs with the working do not have blobs yet + if change_type == 'D': b_path = None deleted_file = True - elif modification_id == 'A': + elif change_type == 'A': a_path = None new_file = True # END add/remove handling - diff = Diff(repo, a_path, b_path, a_blob_id, b_blob_id, old_mode, new_mode, new_file, deleted_file, None, None, '') index.append(diff) -- cgit v1.2.1 From 9840afda82fafcc3eaf52351c64e2cfdb8962397 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 18 Oct 2009 17:09:13 +0200 Subject: diff method now checks for git-diff errrs that can easily occour if the repository is bare and if there is no index or second tree specified --- lib/git/diff.py | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) (limited to 'lib/git') diff --git a/lib/git/diff.py b/lib/git/diff.py index 1774597a..9b884502 100644 --- a/lib/git/diff.py +++ b/lib/git/diff.py @@ -6,7 +6,7 @@ import re import objects.blob as blob - +from errors import GitCommandError class Diffable(object): """ @@ -26,7 +26,7 @@ class Diffable(object): class Index(object): pass - def diff(self, other=None, paths=None, create_patch=False, **kwargs): + def diff(self, other=Index, paths=None, create_patch=False, **kwargs): """ Creates diffs between two items being trees, trees and index or an index and the working tree. @@ -34,7 +34,9 @@ class Diffable(object): ``other`` Is the item to compare us with. If None, we will be compared to the working tree. - If Index ( type ), it will be compared against the index + If Index ( type ), it will be compared against the index. + It defaults to Index to assure the method will not by-default fail + on bare repositories. ``paths`` is a list of paths or a single path to limit the diff to. @@ -53,7 +55,10 @@ class Diffable(object): git.DiffIndex Note - Rename detection will only work if create_patch is True + Rename detection will only work if create_patch is True. + + On a bare repository, 'other' needs to be provided as Index or as + as Tree/Commit, or a git command error will occour """ args = list(self._diff_args[:]) args.append( "--abbrev=40" ) # we need full shas @@ -87,7 +92,13 @@ class Diffable(object): diff_method = Diff._index_from_raw_format if create_patch: diff_method = Diff._index_from_patch_format - return diff_method(self.repo, proc.stdout) + index = diff_method(self.repo, proc.stdout) + + status = proc.wait() + if status != 0: + raise GitCommandError("git-diff", status, proc.stderr ) + + return index class DiffIndex(list): -- cgit v1.2.1 From 657a57adbff49c553752254c106ce1d5b5690cf8 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 18 Oct 2009 17:20:30 +0200 Subject: Improved tagobject message handling by not assuming an empty fourth line anymore --- lib/git/objects/tag.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'lib/git') diff --git a/lib/git/objects/tag.py b/lib/git/objects/tag.py index f54d4b64..b2140551 100644 --- a/lib/git/objects/tag.py +++ b/lib/git/objects/tag.py @@ -60,8 +60,13 @@ class TagObject(base.Object): tagger_info = lines[3][7:]# tagger self.tagger, self.tagged_date = utils.parse_actor_and_date(tagger_info) - # line 4 empty - check git source to figure out purpose - self.message = "\n".join(lines[5:]) + # line 4 empty - it could mark the beginning of the next header + # in csse there really is no message, it would not exist. Otherwise + # a newline separates header from message + if len(lines) > 5: + self.message = "\n".join(lines[5:]) + else: + self.message = '' # END check our attributes else: super(TagObject, self)._set_cache_(attr) -- cgit v1.2.1 From 9513aa01fab73f53e4fe18644c7d5b530a66c6a1 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 18 Oct 2009 23:15:55 +0200 Subject: Added frame for configuration reader involving a meta class, decorators and tests - most of which still has to be filled out --- lib/git/__init__.py | 1 + lib/git/config.py | 100 ++++++++++++++++++++++++++++++++++++++++++++++++++++ lib/git/repo.py | 53 +++++++++++++++++++++++++++- 3 files changed, 153 insertions(+), 1 deletion(-) create mode 100644 lib/git/config.py (limited to 'lib/git') diff --git a/lib/git/__init__.py b/lib/git/__init__.py index e2adac62..041d69f7 100644 --- a/lib/git/__init__.py +++ b/lib/git/__init__.py @@ -9,6 +9,7 @@ import inspect __version__ = 'git' +from git.config import GitConfigParser from git.objects import * from git.refs import * from git.actor import Actor diff --git a/lib/git/config.py b/lib/git/config.py new file mode 100644 index 00000000..c7f2c398 --- /dev/null +++ b/lib/git/config.py @@ -0,0 +1,100 @@ +# config.py +# Copyright (C) 2008, 2009 Michael Trier (mtrier@gmail.com) and contributors +# +# This module is part of GitPython and is released under +# the BSD License: http://www.opensource.org/licenses/bsd-license.php +""" +Module containing module parser implementation able to properly read and write +configuration files +""" + +import re +from ConfigParser import RawConfigParser + +class _MetaParserBuilder(type): + """ + Utlity class wrapping methods into decorators that assure read-only properties + """ + +def _needs_values(func): + """Returns method assuring we read values (on demand) before we try to access them""" + return func + +def _ensure_writable(non_const_func): + """Return method that checks whether given non constant function may be called. + If so, the instance will be set dirty""" + + + +class GitConfigParser(RawConfigParser, object): + """ + Implements specifics required to read git style configuration files. + + This variation behaves much like the git.config command such that the configuration + will be read on demand based on the filepath given during initialization. + + The changes will automatically be written once the instance goes out of scope, but + can be triggered manually as well. + + The configuration file will be locked if you intend to change values preventing other + instances to write concurrently. + """ + __metaclass__ = _MetaParserBuilder + + OPTCRE = re.compile( + r'\s?(?P