Repository: gitpython-developers/GitPython (python)

Issue:
`is_dirty` for directory
I have a feature request: it will be useful to have something similar to `git status <path>` that returns which files are modified, untracked, etc. - in the give directory only.

Similar to `repo.is_dirty()`, but something that accepts path as well.

The usecase: we use git commitish to generate docker tags, and need to know whether a particular module directory is dirty.

Pull request:
is_dirty supports path. Fixes #482.

Patch:
diff --git a/README.md b/README.md
index ad2aa4fc..85983f0d 100644
--- a/README.md
+++ b/README.md
@@ -18,6 +18,9 @@ The list of dependencies are listed in `./requirements.txt` and `./test-requirem
 
 ### INSTALL
 
+[![Latest Version](https://pypip.in/version/GitPython/badge.svg)](https://pypi.python.org/pypi/GitPython/)
+[![Supported Python Versions](https://pypip.in/py_versions/GitPython/badge.svg)](https://pypi.python.org/pypi/GitPython/)
+
 If you have downloaded the source code:
 
     python setup.py install
@@ -98,6 +101,8 @@ New BSD License.  See the LICENSE file.
 [![Build Status](https://travis-ci.org/gitpython-developers/GitPython.svg)](https://travis-ci.org/gitpython-developers/GitPython)
 [![Code Climate](https://codeclimate.com/github/gitpython-developers/GitPython/badges/gpa.svg)](https://codeclimate.com/github/gitpython-developers/GitPython)
 [![Documentation Status](https://readthedocs.org/projects/gitpython/badge/?version=stable)](https://readthedocs.org/projects/gitpython/?badge=stable)
+[![Issue Stats](http://www.issuestats.com/github/gitpython-developers/GitPython/badge/pr)](http://www.issuestats.com/github/gitpython-developers/GitPython)
+[![Issue Stats](http://www.issuestats.com/github/gitpython-developers/GitPython/badge/issue)](http://www.issuestats.com/github/gitpython-developers/GitPython)
 
 Now that there seems to be a massive user base, this should be motivation enough to let git-python return to a proper state, which means
 
@@ -106,4 +111,4 @@ Now that there seems to be a massive user base, this should be motivation enough
 
 [twitch-channel]: http://www.twitch.tv/byronimo/profile
 [youtube-playlist]: https://www.youtube.com/playlist?list=PLMHbQxe1e9MnoEcLhn6Yhv5KAvpWkJbL0
-[contributing]: https://github.com/gitpython-developers/GitPython/blob/master/README.md
+[contributing]: https://github.com/gitpython-developers/GitPython/blob/master/README.md
\ No newline at end of file
diff --git a/VERSION b/VERSION
index f752945d..2c403d06 100644
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-2.0.8dev0
+2.0.7dev0
diff --git a/doc/source/changes.rst b/doc/source/changes.rst
index 9f8ebb51..428fa2b4 100644
--- a/doc/source/changes.rst
+++ b/doc/source/changes.rst
@@ -2,18 +2,6 @@
 Changelog
 =========
 
-2.0.8 - Bugfixes
-================
-
-* `DiffIndex.iter_change_type(...)` produces better results when diffing
-  an index against the working tree.
-
-2.0.7 - New Features
-====================
-
-* `IndexFile.commit(...,skip_hooks=False)` added. This parameter emulates the 
-   behaviour of `--no-verify` on the command-line.
-
 2.0.6 - Fixes and Features
 ==========================
 
diff --git a/doc/source/tutorial.rst b/doc/source/tutorial.rst
index 92020975..b0ef273d 100644
--- a/doc/source/tutorial.rst
+++ b/doc/source/tutorial.rst
@@ -474,13 +474,8 @@ Using environment variables, you can further adjust the behaviour of the git com
 
 * **GIT_PYTHON_TRACE**
 
- * If set to non-0, all executed git commands will be shown as they happen
- * If set to *full*, the executed git command _and_ its entire output on stdout and stderr will be shown as they happen
-
- **NOTE**: All logging is outputted using a Python logger, so make sure your program is configured to show INFO-level messages.  If this is not the case, try adding the following to your program::
- 
-    import logging
-    logging.basicConfig(level=logging.INFO)
+ * If set to non-0, all executed git commands will be logged using a python logger.
+ * if set to *full*, the executed git command and its output on stdout and stderr will be logged using a python logger.
  
 * **GIT_PYTHON_GIT_EXECUTABLE**
 
diff --git a/git/diff.py b/git/diff.py
index fb8faaf6..06193920 100644
--- a/git/diff.py
+++ b/git/diff.py
@@ -183,9 +183,7 @@ class DiffIndex(list):
             raise ValueError("Invalid change type: %s" % change_type)
 
         for diff in self:
-            if diff.change_type == change_type:
-                yield diff
-            elif change_type == "A" and diff.new_file:
+            if change_type == "A" and diff.new_file:
                 yield diff
             elif change_type == "D" and diff.deleted_file:
                 yield diff
@@ -249,12 +247,11 @@ class Diff(object):
     NULL_BIN_SHA = b"\0" * 20
 
     __slots__ = ("a_blob", "b_blob", "a_mode", "b_mode", "a_rawpath", "b_rawpath",
-                 "new_file", "deleted_file", "raw_rename_from", "raw_rename_to",
-                 "diff", "change_type")
+                 "new_file", "deleted_file", "raw_rename_from", "raw_rename_to", "diff")
 
     def __init__(self, repo, a_rawpath, b_rawpath, a_blob_id, b_blob_id, a_mode,
                  b_mode, new_file, deleted_file, raw_rename_from,
-                 raw_rename_to, diff, change_type):
+                 raw_rename_to, diff):
 
         self.a_mode = a_mode
         self.b_mode = b_mode
@@ -289,7 +286,6 @@ class Diff(object):
         self.raw_rename_to = raw_rename_to or None
 
         self.diff = diff
-        self.change_type = change_type
 
     def __eq__(self, other):
         for name in self.__slots__:
@@ -439,7 +435,7 @@ class Diff(object):
                               new_file, deleted_file,
                               rename_from,
                               rename_to,
-                              None, None))
+                              None))
 
             previous_header = header
         # end for each header we parse
@@ -487,7 +483,7 @@ class Diff(object):
             # 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, rename_from, rename_to, '', change_type)
+                        new_file, deleted_file, rename_from, rename_to, '')
             index.append(diff)
         # END for each line
 
diff --git a/git/ext/gitdb b/git/ext/gitdb
index 2389b752..d1996e04 160000
--- a/git/ext/gitdb
+++ b/git/ext/gitdb
@@ -1,1 +1,1 @@
-Subproject commit 2389b75280efb1a63e6ea578eae7f897fd4beb1b
+Subproject commit d1996e04dbf4841b853b60c1365f0f5fd28d170c
diff --git a/git/refs/symbolic.py b/git/refs/symbolic.py
index ec2944c6..ae67a7ee 100644
--- a/git/refs/symbolic.py
+++ b/git/refs/symbolic.py
@@ -313,7 +313,7 @@ class SymbolicReference(object):
 
         lfd = LockedFD(fpath)
         fd = lfd.open(write=True, stream=True)
-        fd.write(write_value.encode('ascii') + b'\n')
+        fd.write(write_value.encode('ascii'))
         lfd.commit()
 
         # Adjust the reflog
diff --git a/git/repo/base.py b/git/repo/base.py
index 61864060..5d0a6d30 100644
--- a/git/repo/base.py
+++ b/git/repo/base.py
@@ -585,7 +585,7 @@ class Repo(object):
                           doc="Retrieve a list of alternates paths or set a list paths to be used as alternates")
 
     def is_dirty(self, index=True, working_tree=True, untracked_files=False,
-                 submodules=True):
+                 submodules=True, path=None):
         """
         :return:
             ``True``, the repository is considered dirty. By default it will react
@@ -600,6 +600,8 @@ class Repo(object):
         default_args = ['--abbrev=40', '--full-index', '--raw']
         if not submodules:
             default_args.append('--ignore-submodules')
+        if path:
+            default_args.append(path)
         if index:
             # diff index against HEAD
             if isfile(self.index.path) and \
@@ -612,7 +614,7 @@ class Repo(object):
                 return True
         # END working tree handling
         if untracked_files:
-            if len(self._get_untracked_files(ignore_submodules=not submodules)):
+            if len(self._get_untracked_files(path, ignore_submodules=not submodules)):
                 return True
         # END untracked files
         return False
@@ -633,9 +635,10 @@ class Repo(object):
             consider caching it yourself."""
         return self._get_untracked_files()
 
-    def _get_untracked_files(self, **kwargs):
+    def _get_untracked_files(self, *args, **kwargs):
         # make sure we get all files, no only untracked directores
-        proc = self.git.status(porcelain=True,
+        proc = self.git.status(*args,
+                               porcelain=True,
                                untracked_files=True,
                                as_process=True,
                                **kwargs)
@@ -714,7 +717,8 @@ class Repo(object):
                            authored_date=int(props[b'author-time']),
                            committer=Actor(safe_decode(props[b'committer']),
                                            safe_decode(props[b'committer-mail'].lstrip(b'<').rstrip(b'>'))),
-                           committed_date=int(props[b'committer-time']))
+                           committed_date=int(props[b'committer-time']),
+                           message=safe_decode(props[b'summary']))
                 commits[hexsha] = c
             else:
                 # Discard the next line (it's a filename end tag)
@@ -814,7 +818,8 @@ class Repo(object):
                                            authored_date=info['author_date'],
                                            committer=Actor._from_string(
                                                info['committer'] + ' ' + info['committer_email']),
-                                           committed_date=info['committer_date'])
+                                           committed_date=info['committer_date'],
+                                           message=info['summary'])
                                 commits[sha] = c
                             # END if commit objects needs initial creation
                             if not is_binary:


Test patch:
diff --git a/git/test/fixtures/diff_abbrev-40_full-index_M_raw_no-color b/git/test/fixtures/diff_abbrev-40_full-index_M_raw_no-color
deleted file mode 100644
index dad85c68..00000000
--- a/git/test/fixtures/diff_abbrev-40_full-index_M_raw_no-color
+++ /dev/null
@@ -1,1 +0,0 @@
-:100644 100644 739bc65220ad90e9ebfa2d6af1723b97555569a4 0000000000000000000000000000000000000000 M	README.md
diff --git a/git/test/test_diff.py b/git/test/test_diff.py
index 9fdb26a2..ba0d2d13 100644
--- a/git/test/test_diff.py
+++ b/git/test/test_diff.py
@@ -104,15 +104,6 @@ class TestDiff(TestBase):
         assert diff.rename_to == 'that'
         assert len(list(diffs.iter_change_type('R'))) == 1
 
-    def test_diff_of_modified_files_not_added_to_the_index(self):
-        output = StringProcessAdapter(fixture('diff_abbrev-40_full-index_M_raw_no-color'))
-        diffs = Diff._index_from_raw_format(self.rorepo, output.stdout)
-        
-        assert len(diffs) == 1, 'one modification'
-        assert len(list(diffs.iter_change_type('M'))) == 1, 'one modification'
-        assert diffs[0].change_type == 'M'
-        assert diffs[0].b_blob is None
-        
     def test_binary_diff(self):
         for method, file_name in ((Diff._index_from_patch_format, 'diff_patch_binary'),
                                   (Diff._index_from_raw_format, 'diff_raw_binary')):
diff --git a/git/test/test_repo.py b/git/test/test_repo.py
index 87887bad..7ccd173e 100644
--- a/git/test/test_repo.py
+++ b/git/test/test_repo.py
@@ -271,6 +271,24 @@ class TestRepo(TestBase):
         assert self.rorepo.is_dirty() is False
         self.rorepo._bare = orig_val
 
+    @with_rw_repo('HEAD')
+    def test_is_dirty_with_path(self, rwrepo):
+        assert rwrepo.is_dirty(path="git") is False
+
+        with open(os.path.join(rwrepo.working_dir, "git", "util.py"), "at") as f:
+            f.write("junk")
+        assert rwrepo.is_dirty(path="git") is True
+        assert rwrepo.is_dirty(path="doc") is False
+
+        rwrepo.git.add(os.path.join("git", "util.py"))
+        assert rwrepo.is_dirty(index=False, path="git") is False
+        assert rwrepo.is_dirty(path="git") is True
+
+        with open(os.path.join(rwrepo.working_dir, "doc", "no-such-file.txt"), "wt") as f:
+            f.write("junk")
+        assert rwrepo.is_dirty(path="doc") is False
+        assert rwrepo.is_dirty(untracked_files=True, path="doc") is True
+
     def test_head(self):
         assert self.rorepo.head.reference.object == self.rorepo.active_branch.object
 
@@ -307,7 +325,7 @@ class TestRepo(TestBase):
         assert_equal('Tom Preston-Werner', c.committer.name)
         assert_equal('tom@mojombo.com', c.committer.email)
         assert_equal(1191997100, c.committed_date)
-        self.assertRaisesRegexp(ValueError, "634396b2f541a9f2d58b00be1a07f0c358b999b3 missing", lambda: c.message)
+        assert_equal('initial grit setup', c.message)
 
         # test the 'lines per commit' entries
         tlist = b[0][1]