Skip to content
This repository has been archived by the owner on Oct 13, 2021. It is now read-only.

Commit

Permalink
Display the last-committed time as modified column when possible.
Browse files Browse the repository at this point in the history
Fall back to stat'ing the file if VCS does not find it.

Merges #564.
  • Loading branch information
pelmers committed Jul 11, 2016
2 parents 1e140d0 + 0b298ee commit 270077c
Show file tree
Hide file tree
Showing 8 changed files with 112 additions and 22 deletions.
13 changes: 12 additions & 1 deletion dxr/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,17 @@ def _browse_folder(tree, path, config):
listing. Otherwise, raise NotFound.
"""
def item_or_list(item):
"""If item is a list, return its first element.
Otherwise, just return it.
"""
# TODO @pelmers: remove this function when format bumps to 20
if isinstance(item, list):
return item[0]
return item

frozen = frozen_config(tree)

files_and_folders = filtered_query(
Expand Down Expand Up @@ -403,7 +414,7 @@ def _browse_folder(tree, path, config):
files_and_folders=[
(_icon_class_name(f),
f['name'],
decode_es_datetime(f['modified']) if 'modified' in f else None,
decode_es_datetime(item_or_list(f['modified'])) if 'modified' in f else None,
f.get('size'),
url_for('.browse', tree=tree, path=f.get('link', f['path'])[0]))
for f in files_and_folders])
Expand Down
1 change: 0 additions & 1 deletion dxr/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -499,7 +499,6 @@ def docs():
folder=folder_name,
name=file_name,
size=file_info.st_size,
modified=datetime.fromtimestamp(file_info.st_mtime),
is_folder=False,

# And these, which all get mashed into arrays:
Expand Down
10 changes: 6 additions & 4 deletions dxr/hgext/previous_revisions.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,18 @@
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.


def previous_revisions(ui, repo, **opts):
"""Print the last node in which each file changed."""
"""Print the last node@date@file in which each file changed."""
last_change = {}
for rev in range(0, repo['tip'].rev() + 1):
ctx = repo[rev]
# Go through all filenames changed in this commit
for filename in ctx.files():
last_change[filename] = ctx.hex()
for filename, node in last_change.iteritems():
ui.write('%s:%s\n' % (node, filename))
# Date is returned as a (timestamp, timezone) tuple.
last_change[filename] = (ctx.hex(), ctx.date()[0])
for filename, (node, date) in last_change.iteritems():
ui.write('%s@%s@%s\n' % (node, date, filename))

cmdtable = {
'previous-revisions': (previous_revisions, [], '')
Expand Down
16 changes: 16 additions & 0 deletions dxr/plugins/core.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
"""Core, non-language-specific features of DXR, implemented as a Plugin"""

from base64 import b64encode
from datetime import datetime
from itertools import chain
from os import stat
from os.path import relpath, splitext, realpath, basename
import re

Expand Down Expand Up @@ -474,6 +476,20 @@ def needles(self):
# binary, but not an image
elif not self.contains_text():
yield 'is_binary', True
# Find the last modified time from version control if possible,
# otherwise fall back to the timestamp from stat'ing the file.
modified = None
if self.vcs:
vcs_relative_path = relpath(self.absolute_path(),
self.vcs.get_root_dir())
try:
modified = self.vcs.last_modified_date(vcs_relative_path)
except NotImplementedError:
pass
if modified is None:
file_info = stat(self.absolute_path())
modified = datetime.utcfromtimestamp(file_info.st_mtime)
yield 'modified', modified

def needles_by_line(self):
"""Fill out line number and content for every line."""
Expand Down
8 changes: 6 additions & 2 deletions dxr/templates/folder.html
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,19 @@
<thead>
<tr>
<th scope="col">Name</th>
<th scope="col">Modified</th>
<th scope="col">Modified (UTC)</th>
<th scope="col">Size</th>
</tr>
</thead>
<tbody>
{% for icon, name, modified, size, relative_url in files_and_folders %}
<tr>
<td><a href="{{ relative_url }}" class="icon {{ icon }}">{{ name }}</a></td>
<td><a href="{{ relative_url }}">{{ '' if modified is none else modified.strftime("%Y %b %d %H:%m") }}</a></td>
<td><a href="{{ relative_url }}">
{% if modified is not none %}
<time>{{ modified.strftime("%Y %b %d %H:%m") }}</time>
{% endif %}
</a></td>
<td><a href="{{ relative_url }}">{{ '' if size is none else size|filesizeformat }}</a></td>
</tr>
{% endfor %}
Expand Down
68 changes: 54 additions & 14 deletions dxr/vcs.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
- Check if the mercurial paths are specific to Mozilla's customization or not.
"""
from datetime import datetime
import marshal
import os
from os.path import relpath, join, split
Expand Down Expand Up @@ -57,7 +58,7 @@ def invoke_vcs(cls, args, cwd, **kwargs):

def is_tracked(self, path):
"""Does the repository track this file?"""
return NotImplemented
raise NotImplementedError

def has_upstream(self):
"""Return true if this VCS has a usable upstream."""
Expand All @@ -68,29 +69,35 @@ def has_upstream(self):

def generate_log(self, path):
"""Construct URL to upstream view of log of file at path."""
return NotImplemented
raise NotImplementedError

def generate_diff(self, path):
"""Construct URL to upstream view of diff of file at path."""
return NotImplemented
raise NotImplementedError

def generate_blame(self, path):
"""Construct URL to upstream view of blame on file at path."""
return NotImplemented
raise NotImplementedError

def generate_raw(self, path):
"""Construct URL to upstream view to raw file at path."""
return NotImplemented
raise NotImplementedError

def last_modified_date(self, path):
"""Return a datetime object that represents the last UTC a commit was
made to the given path.
"""
raise NotImplementedError

@classmethod
def get_contents(cls, path, revision, stderr=None):
"""Return contents of file at specified path at given revision, where path is an
absolute path."""
return NotImplemented
raise NotImplementedError

def display_rev(self, path):
"""Return a human-readable revision identifier for the repository."""
return NotImplemented
raise NotImplementedError


class Mercurial(Vcs):
Expand All @@ -103,7 +110,7 @@ def __init__(self, root):
configs=['extensions.previous_revisions=%s' % hgext]) as client:
tip = client.tip()
self.revision = tip.node
self.previous_revisions = self.find_previous_revisions(client)
self.previous_revisions = self._find_previous_revisions(client)
self.upstream = self._construct_upstream_url()

def has_upstream(self):
Expand All @@ -130,16 +137,17 @@ def _construct_upstream_url(self):
recomb[3] = recomb[4] = recomb[5] = '' # Just those three
return urlparse.urlunparse(recomb)

def find_previous_revisions(self, client):
"""Find the last revision in which each file changed, for diff links.
def _find_previous_revisions(self, client):
"""Find the last revision and date in which each file changed, for diff
links and timestamps..
Return a mapping {path: last commit nodes in which file at path changed}
Return a mapping {path: date, last commit nodes in which file at path changed}
"""
last_change = {}
for line in client.rawcommand(['previous-revisions']).splitlines():
node, path = line.split(':', 1)
last_change[path] = node
commit, date, path = line.split('@', 2)
last_change[path] = (commit, datetime.utcfromtimestamp(float(date)))
return last_change

@classmethod
Expand All @@ -160,12 +168,16 @@ def display_rev(self, path):
def is_tracked(self, path):
return path in self.previous_revisions

def last_modified_date(self, path):
if path in self.previous_revisions:
return self.previous_revisions[path][1]

def generate_raw(self, path):
return self.upstream + 'raw-file/' + self.revision + '/' + path

def generate_diff(self, path):
# We generate link to diff with the last revision in which the file changed.
return self.upstream + 'diff/' + self.previous_revisions[path] + '/' + path
return self.upstream + 'diff/' + self.previous_revisions[path][0] + '/' + path

def generate_blame(self, path):
return self.upstream + 'annotate/' + self.revision + '/' + path
Expand All @@ -188,6 +200,31 @@ def __init__(self, root):
self.invoke_vcs(['ls-files'], self.root).splitlines())
self.revision = self.invoke_vcs(['rev-parse', 'HEAD'], self.root).strip()
self.upstream = self._construct_upstream_url()
self.last_changed = self._find_last_changed()

def _find_last_changed(self):
"""Return map {path: date of last authored change}
"""
consume_date = True
current_date = None
last_changed = {}
for line in self.invoke_vcs(
['log', '--format=format:%at', '--name-only'], self.root).splitlines():
# Commits are separated by empty lines.
if not line:
# Then the next line is a date.
consume_date = True
else:
if consume_date:
current_date = datetime.utcfromtimestamp(float(line))
consume_date = False
else:
# Then the line should have a file path, record it if we have
# not seen it and it's tracked.
if line in self.tracked_files and line not in last_changed:
last_changed[line] = current_date
return last_changed


def has_upstream(self):
return self.upstream != ""
Expand All @@ -213,6 +250,9 @@ def _construct_upstream_url(self):
break
return ""

def last_modified_date(self, path):
return self.last_changed.get(path)

@classmethod
def claim_vcs_source(cls, path, dirs, tree):
if '.git' in dirs:
Expand Down
10 changes: 10 additions & 0 deletions tests/test_vcs_git/test_vcs_git.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,16 @@ def test_deep_permalink(self):
eq_(response.status_code, 200)
ok_("This file tests" in response.data)

def test_mdates(self):
"""Make sure that modified dates listed in browse view are dates of
the last commit to the file.
"""
response = self.client().get('/code/source/').data
# main.c
ok_('<time>2015 May 26 18:05</time>' in response)
# binary_file
ok_('<time>2016 Apr 07 21:04</time>' in response)

def test_pygmentize(self):
"""Check that the pygmentize FileToSkim correctly colors a file from permalink."""
client = self.client()
Expand Down
8 changes: 8 additions & 0 deletions tests/test_vcs_hg/test_vcs_hg.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,11 @@ def test_permalink(self):
ok_('/rev/84798105c9ab5897f8c7d630d133d9003b44a62f/Colon:%20name" title="Permalink" class="permalink icon">Permalink</a>' in response.data)
response = self.client().get('/code/rev/84798105c9ab5897f8c7d630d133d9003b44a62f/Colon: name')
eq_(response.status_code, 200)

def test_mdates(self):
"""Make sure that modified dates listed in browse view are dates of
the last commit to the file.
"""
response = self.client().get('/code/source/').data
ok_('<time>2014 Nov 06 19:11</time>' in response)
ok_('<time>2014 Oct 30 20:10</time>' in response)

0 comments on commit 270077c

Please sign in to comment.