teach the scanner to look at uncommitted files

Update the Scanner to look at staged and unstaged changes to files in
the local copy of the repository. We can't detect unknown files, yet.

Closes-Bug: #1553155
Change-Id: I77ed60e6f8b8f819aabb361f34cf779623907f7b
Signed-off-by: Doug Hellmann <doug@doughellmann.com>
This commit is contained in:
Doug Hellmann
2016-12-22 16:47:19 -05:00
parent 0043296ec8
commit f8fc8f97ff
3 changed files with 154 additions and 1 deletions

View File

@ -0,0 +1,7 @@
---
features:
- |
Include the local working copy when scanning the history of the
current branch. Notes files must at least be staged to indicate
that they will eventually be part of the history, but subsequent
changes to the file do not need to also be staged to be seen.

View File

@ -20,7 +20,9 @@ import re
import sys
from dulwich import diff_tree
from dulwich import index as d_index
from dulwich import objects
from dulwich import porcelain
from dulwich import repo
LOG = logging.getLogger(__name__)
@ -423,7 +425,23 @@ class RenoRepo(repo.Repo):
return tree
def get_file_at_commit(self, filename, sha):
"Return the contents of the file if it exists at the commit, or None."
"""Return the contents of the file.
If sha is None, return the working copy of the file. If the
file cannot be read from the working dir, return None.
If the sha is not None and the file exists at the commit,
return the data from the stored blob. If the file does not
exist at the commit, return None.
"""
if sha is None:
# Get the copy from the working directory.
try:
with open(os.path.join(self.path, filename), 'r') as f:
return f.read()
except IOError:
return None
# Get the tree associated with the commit identified by the
# input SHA, then look through the items in the tree to find
# the one with the path matching the filename. Take the
@ -763,10 +781,48 @@ class Scanner(object):
LOG.debug('current repository version: %s' % current_version)
if current_version not in versions_by_date:
versions_by_date.insert(0, current_version)
versions_by_date.insert(0, '*working-copy*')
# Track the versions we have seen and the earliest version for
# which we have seen a given note's unique id.
tracker = _ChangeTracker()
# Process the local index, if we are scanning the current
# branch.
if not branch:
prefix = notesdir.rstrip('/') + '/'
index = self._repo.open_index()
# Pretend anything known to the repo and changed but not
# staged is part of the fake version '*working-copy*'.
LOG.debug('scanning unstaged changes')
for fname in d_index.get_unstaged_changes(index, self.reporoot):
fname = fname.decode('utf-8')
LOG.debug('found unstaged file %s', fname)
if fname.startswith(prefix) and _note_file(fname):
fullpath = os.path.join(self.reporoot, fname)
if os.path.exists(fullpath):
LOG.debug('found file %s', fullpath)
tracker.add(fname, None, '*working-copy*')
else:
LOG.debug('deleted file %s', fullpath)
tracker.delete(fname, None, '*working-copy*')
# Pretend anything in the index is part of the fake
# version "*working-copy*".
LOG.debug('scanning staged schanges')
changes = porcelain.get_tree_changes(self._repo)
for fname in changes['add']:
fname = fname.decode('utf-8')
tracker.add(fname, None, '*working-copy*')
for fname in changes['modify']:
fname = fname.decode('utf-8')
tracker.modify(fname, None, '*working-copy*')
for fname in changes['delete']:
fname = fname.decode('utf-8')
tracker.delete(fname, None, '*working-copy*')
# Process the git commit history.
for counter, entry in enumerate(self._topo_traversal(branch), 1):
sha = entry.commit.id

View File

@ -20,6 +20,7 @@ import os.path
import re
import subprocess
import time
import unittest
from dulwich import diff_tree
from dulwich import objects
@ -529,6 +530,82 @@ class BasicTest(Base):
results,
)
def test_staged_file(self):
# Prove that we can get a file we have staged.
# Start with a standard commit and tag
self._make_python_package()
self.repo.git('tag', '-s', '-m', 'first tag', '1.0.0')
# Now stage a release note
n = self.get_note_num()
basename = 'staged-note-%016x.yaml' % n
filename = os.path.join(self.reporoot, 'releasenotes', 'notes',
basename)
create._make_note_file(filename, 'staged note')
self.repo.git('add', filename)
status_results = self.repo.git('status')
self.addDetail('git status', text_content(status_results))
# Now run the scanner
self.scanner = scanner.Scanner(self.c)
raw_results = self.scanner.get_notes_by_version()
self.assertEqual(
{'*working-copy*': [
(os.path.join('releasenotes', 'notes', basename),
None)],
},
raw_results,
)
@unittest.skip('dulwich does not know how to identify new files')
def test_added_tagged_not_staged(self):
# Prove that we can get a file we have created but not staged.
# Start with a standard commit and tag
self._make_python_package()
self.repo.git('tag', '-s', '-m', 'first tag', '1.0.0')
# Now create a note without staging it
n = self.get_note_num()
basename = 'staged-note-%016x.yaml' % n
filename = os.path.join(self.reporoot, 'releasenotes', 'notes',
basename)
create._make_note_file(filename, 'staged note')
status_results = self.repo.git('status')
self.addDetail('git status', text_content(status_results))
# Now run the scanner
self.scanner = scanner.Scanner(self.c)
raw_results = self.scanner.get_notes_by_version()
# Take the staged version of the file, but associate it with
# tagged version 1.0.0 because the file was added before that
# version.
self.assertEqual(
{'1.0.0': [(os.path.join('releasenotes', 'notes', basename),
None)],
},
raw_results,
)
def test_modified_tagged_not_staged(self):
# Prove that we can get a file we have changed but not staged.
# Start with a standard commit and tag
self._make_python_package()
f1 = self._add_notes_file('slug1')
self.repo.git('tag', '-s', '-m', 'first tag', '1.0.0')
# Now modify the note
fullpath = os.path.join(self.repo.reporoot, f1)
with open(fullpath, 'w') as f:
f.write('modified first note')
status_results = self.repo.git('status')
self.addDetail('git status', text_content(status_results))
# Now run the scanner
self.scanner = scanner.Scanner(self.c)
raw_results = self.scanner.get_notes_by_version()
# Take the staged version of the file, but associate it with
# tagged version 1.0.0 because the file was added before that
# version.
self.assertEqual(
{'1.0.0': [(f1, None)],
},
raw_results,
)
class FileContentsTest(Base):
@ -597,6 +674,19 @@ class FileContentsTest(Base):
contents,
)
def test_staged_file(self):
# Prove we are not picking up the contents from the local
# filesystem outside of the git history.
f1 = self._add_notes_file(contents='initial-contents')
with open(os.path.join(self.reporoot, f1), 'w') as f:
f.write('new contents for file')
r = scanner.RenoRepo(self.reporoot)
contents = r.get_file_at_commit(f1, None)
self.assertEqual(
'new contents for file',
contents,
)
class PreReleaseTest(Base):