mirror of
https://github.com/haiwen/seahub.git
synced 2025-08-02 07:47:32 +00:00
add text file diff
This commit is contained in:
parent
1f08f9fb52
commit
f9e0437628
@ -44,6 +44,7 @@ class FileContributors(models.Model):
|
||||
file_path_hash = models.CharField(max_length=12)
|
||||
|
||||
last_modified = models.BigIntegerField()
|
||||
last_commit_id = models.CharField(max_length=40)
|
||||
|
||||
# email addresses seperated by comma
|
||||
emails = models.TextField()
|
||||
|
@ -928,7 +928,7 @@ ul.with-bg li {
|
||||
color:#666;
|
||||
margin-left:5px;
|
||||
}
|
||||
.lsch, .lsch-encrypted {
|
||||
.lsch, .lsch-encrypted, .file-diff {
|
||||
font-size:12px;
|
||||
font-weight:normal;
|
||||
color:#666;
|
||||
@ -1378,3 +1378,16 @@ ul.with-bg li {
|
||||
}
|
||||
.placeholder { color: #aaa; }
|
||||
|
||||
/* text file diff */
|
||||
div.diff-desc { margin: 10px 0; }
|
||||
div.diff-desc p { margin: 10px 0; }
|
||||
#text-diff-output table { margin-top: 20px; border: 1px solid gray; }
|
||||
#text-diff-output thead { border: 1px solid gray; }
|
||||
#text-diff-output th { background-color: #EED; text-align: center; }
|
||||
#text-diff-output th, #text-diff-output td { border-bottom: none; }
|
||||
#text-diff-output tr.hl { background-color: #afcde8; }/*highlight*/
|
||||
|
||||
.diff_header { background-color: #EED; text-align: center; }
|
||||
.diff_add { background-color: #DFD; }
|
||||
.diff_sub { background-color: #FDD; }
|
||||
.diff_chg { background-color: #FD8; }
|
||||
|
@ -82,7 +82,13 @@
|
||||
{% if not view_history %}
|
||||
<div id="file-commit-info">
|
||||
<div class="latest-commit ovhd">
|
||||
<p class="latest-commit-info fleft">{% avatar latest_contributor 20 %} <a href="{% url 'user_profile' latest_contributor %}" class="name">{{ latest_contributor|email2nickname }}</a> <span class="time">{{ last_modified|translate_commit_time}}</span><span> 做了最新修改</span></p>
|
||||
<p class="latest-commit-info fleft">{% avatar latest_contributor 20 %} <a href="{% url 'user_profile' latest_contributor %}" class="name">{{ latest_contributor|email2nickname }}</a> <span class="time">{{ last_modified|translate_commit_time}}</span><span> 做了最新修改</span>
|
||||
{% if filetype == 'Text' or filetype == 'Markdown' %}
|
||||
{% if last_commit_id %}
|
||||
<span><a class="file-diff" href="{% url 'text_diff' repo.id %}?p={{path|urlencode}}&commit={{last_commit_id}}">详情</a></span>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</p>
|
||||
<a href="{{ SITE_ROOT }}repo/file_revisions/{{ repo.id }}/?p={{ path }}" class="more fright">更多历史</a>
|
||||
</div>
|
||||
<p class="contributors">
|
||||
|
@ -14,3 +14,6 @@
|
||||
{% if filetype == 'Markdown' %}
|
||||
<script type="text/javascript" src="{{MEDIA_URL}}js/showdown.js"></script>
|
||||
{% endif %}
|
||||
|
||||
{% if filetype == 'Text' or filetype == 'Markdown' %}
|
||||
{% endif %}
|
||||
|
39
templates/text_diff.html
Normal file
39
templates/text_diff.html
Normal file
@ -0,0 +1,39 @@
|
||||
{% extends base_template %}
|
||||
{% load seahub_tags avatar_tags%}
|
||||
{% load url from future %}
|
||||
|
||||
{% block main_panel %}
|
||||
<h2>文件修改详情</h2>
|
||||
<div class="diff-desc">
|
||||
<p class="path">
|
||||
文件路径:
|
||||
{% for name, link in zipped %}
|
||||
{% if not forloop.last %}
|
||||
<a href="{% url 'repo' repo.id %}?p={{ link|urlencode }}">{{ name }}</a> /
|
||||
{% else %}
|
||||
<a href="{% url 'repo_view_file' repo.id %}?p={{ link|urlencode }}">{{ name }}</a>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</p>
|
||||
<p class="">{% avatar current_commit.creator_name 20 %} {{ current_commit.creator_name|email2nickname }} 修改于 {{ current_commit.ctime|translate_commit_time }}</p>
|
||||
</div>
|
||||
{% if is_new_file %}
|
||||
<div class="text-panel">
|
||||
<p>文件 {{repo.name}} {{ path }} 是一个新建的空白文件</p>
|
||||
</div>
|
||||
{% else %}
|
||||
<div id="text-diff-output">
|
||||
<table rules="group">
|
||||
<thead>
|
||||
<th width="3%"></th>
|
||||
<th width="47%"> 修改前 </th>
|
||||
<th width="3%"></th>
|
||||
<th width="47%"> 修改后 </th>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{ diff_result_table|safe }}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
@ -28,3 +28,5 @@ from service import get_related_users_by_repo, get_related_users_by_org_repo
|
||||
|
||||
from service import CCNET_CONF_PATH, CCNET_SERVER_ADDR, CCNET_SERVER_PORT
|
||||
|
||||
from htmldiff import HtmlDiff
|
||||
|
||||
|
2052
thirdpart/seaserv/htmldiff.py
Normal file
2052
thirdpart/seaserv/htmldiff.py
Normal file
File diff suppressed because it is too large
Load Diff
1
urls.py
1
urls.py
@ -56,6 +56,7 @@ urlpatterns = patterns('',
|
||||
(r'^repo/upload_error/(?P<repo_id>[^/]+)/$', upload_file_error),
|
||||
(r'^repo/update_error/(?P<repo_id>[^/]+)/$', update_file_error),
|
||||
url(r'^repo/file_revisions/(?P<repo_id>[^/]+)/$', file_revisions, name='file_revisions'),
|
||||
url(r'^repo/text_diff/(?P<repo_id>[^/]+)/$', text_diff, name='text_diff'),
|
||||
url(r'^repo/(?P<repo_id>[^/]{36,36})/$', RepoView.as_view(), name='repo'),
|
||||
(r'^repo/history/(?P<repo_id>[^/]+)/$', repo_history),
|
||||
(r'^repo/history/revert/(?P<repo_id>[^/]+)/$', repo_history_revert),
|
||||
|
16
utils.py
16
utils.py
@ -367,7 +367,7 @@ def get_file_contributors_from_revisions(repo_id, file_path):
|
||||
if user not in ret:
|
||||
ret.append(user)
|
||||
|
||||
return ret, commits[0].ctime
|
||||
return ret, commits[0].ctime, commits[0].id
|
||||
|
||||
def get_file_contributors(repo_id, file_path, file_path_hash, file_id):
|
||||
"""Get file contributors list and last modfied time from database cache.
|
||||
@ -376,12 +376,13 @@ def get_file_contributors(repo_id, file_path, file_path_hash, file_id):
|
||||
"""
|
||||
contributors = []
|
||||
last_modified = 0
|
||||
last_commit_id = ''
|
||||
try:
|
||||
fc = FileContributors.objects.get(repo_id=repo_id,
|
||||
file_path_hash=file_path_hash)
|
||||
except FileContributors.DoesNotExist:
|
||||
# has no cache yet
|
||||
contributors, last_modified = get_file_contributors_from_revisions (repo_id, file_path)
|
||||
contributors, last_modified, last_commit_id = get_file_contributors_from_revisions (repo_id, file_path)
|
||||
if not contributors:
|
||||
return [], 0
|
||||
emails = ','.join(contributors)
|
||||
@ -390,22 +391,24 @@ def get_file_contributors(repo_id, file_path, file_path_hash, file_id):
|
||||
file_path=file_path,
|
||||
file_path_hash=file_path_hash,
|
||||
last_modified=last_modified,
|
||||
last_commit_id=last_commit_id,
|
||||
emails=emails)
|
||||
file_contributors.save()
|
||||
else:
|
||||
# cache found
|
||||
if fc.file_id != file_id:
|
||||
if fc.file_id != file_id or not fc.last_commit_id:
|
||||
# but cache is outdated
|
||||
fc.delete()
|
||||
contributors, last_modified = get_file_contributors_from_revisions (repo_id, file_path)
|
||||
contributors, last_modified, last_commit_id = get_file_contributors_from_revisions (repo_id, file_path)
|
||||
if not contributors:
|
||||
return [], 0
|
||||
return [], 0, ''
|
||||
emails = ','.join(contributors)
|
||||
file_contributors = FileContributors(repo_id=repo_id,
|
||||
file_id=file_id,
|
||||
file_path=file_path,
|
||||
file_path_hash=file_path_hash,
|
||||
last_modified=last_modified,
|
||||
last_commit_id=last_commit_id,
|
||||
emails=emails)
|
||||
file_contributors.save()
|
||||
else:
|
||||
@ -415,8 +418,9 @@ def get_file_contributors(repo_id, file_path, file_path_hash, file_id):
|
||||
else:
|
||||
contributors = []
|
||||
last_modified = fc.last_modified
|
||||
last_commit_id = fc.last_commit_id
|
||||
|
||||
return contributors, last_modified
|
||||
return contributors, last_modified, last_commit_id
|
||||
|
||||
|
||||
if hasattr(settings, 'EVENTS_CONFIG_FILE'):
|
||||
|
91
views.py
91
views.py
@ -42,7 +42,7 @@ from seaserv import ccnet_rpc, ccnet_threaded_rpc, get_repos, get_emailusers, \
|
||||
list_personal_shared_repos, is_org_group, get_org_id_by_group, is_org_repo,\
|
||||
list_inner_pub_repos, get_org_groups_by_repo, is_org_repo_owner, \
|
||||
get_org_repo_owner, is_passwd_set, get_file_size, \
|
||||
get_related_users_by_repo, get_related_users_by_org_repo
|
||||
get_related_users_by_repo, get_related_users_by_org_repo, HtmlDiff
|
||||
from pysearpc import SearpcError
|
||||
|
||||
from signals import repo_created, repo_deleted
|
||||
@ -77,7 +77,6 @@ except ImportError:
|
||||
DOCUMENT_CONVERTOR_ROOT = None
|
||||
from settings import FILE_PREVIEW_MAX_SIZE, INIT_PASSWD
|
||||
|
||||
|
||||
@login_required
|
||||
def root(request):
|
||||
return HttpResponseRedirect(reverse(myhome))
|
||||
@ -786,7 +785,7 @@ def repo_history_changes(request, repo_id):
|
||||
|
||||
return HttpResponse(json.dumps(changes),
|
||||
content_type=content_type)
|
||||
|
||||
|
||||
@login_required
|
||||
def modify_token(request, repo_id):
|
||||
if not validate_owner(request, repo_id):
|
||||
@ -1290,7 +1289,7 @@ def repo_view_file(request, repo_id):
|
||||
file_path_hash = md5_constructor(urllib2.quote(path.encode('utf-8'))).hexdigest()[:12]
|
||||
comments = FileComment.objects.filter(file_path_hash=file_path_hash, repo_id=repo_id)
|
||||
|
||||
contributors, last_modified = get_file_contributors(repo_id, path.encode('utf-8'), file_path_hash, obj_id)
|
||||
contributors, last_modified, last_commit_id = get_file_contributors(repo_id, path.encode('utf-8'), file_path_hash, obj_id)
|
||||
latest_contributor = contributors[0]
|
||||
|
||||
return render_to_response('repo_view_file.html', {
|
||||
@ -1323,6 +1322,7 @@ def repo_view_file(request, repo_id):
|
||||
'contributors': contributors,
|
||||
'latest_contributor': latest_contributor,
|
||||
'last_modified': last_modified,
|
||||
'last_commit_id': last_commit_id,
|
||||
'read_only': read_only,
|
||||
'page_from': page_from,
|
||||
}, context_instance=RequestContext(request))
|
||||
@ -2610,3 +2610,86 @@ def repo_set_password(request):
|
||||
|
||||
return HttpResponse(json.dumps(ret),
|
||||
content_type=content_type)
|
||||
|
||||
def get_file_content_by_commit_and_path(request, repo_id, commit_id, path):
|
||||
try:
|
||||
obj_id = seafserv_threaded_rpc.get_file_id_by_commit_and_path( \
|
||||
commit_id, path)
|
||||
except:
|
||||
return None, 'bad path'
|
||||
|
||||
if not obj_id or obj_id == EMPTY_SHA1:
|
||||
return '', None
|
||||
else:
|
||||
permission = get_user_permission(request, repo_id)
|
||||
if permission:
|
||||
# Get a token to visit file
|
||||
token = seafserv_rpc.web_get_access_token(repo_id,
|
||||
obj_id,
|
||||
'view',
|
||||
request.user.username)
|
||||
else:
|
||||
return None, 'permission denied'
|
||||
|
||||
filename = os.path.basename(path)
|
||||
raw_path = gen_file_get_url(token, urllib2.quote(filename))
|
||||
|
||||
try:
|
||||
err, file_content, encoding, newline_mode = repo_file_get(raw_path)
|
||||
except Exception, e:
|
||||
return None, 'error when read file from httpserver: %s' % e
|
||||
return file_content, err
|
||||
|
||||
@login_required
|
||||
def text_diff(request, repo_id):
|
||||
commit_id = request.GET.get('commit', '')
|
||||
path = request.GET.get('p', '')
|
||||
|
||||
if not (commit_id and path):
|
||||
return render_error(request, 'bad params')
|
||||
|
||||
repo = get_repo(repo_id)
|
||||
if not repo:
|
||||
return render_error(request, 'bad repo')
|
||||
|
||||
current_commit = seafserv_threaded_rpc.get_commit(commit_id)
|
||||
if not current_commit:
|
||||
return render_error(request, 'bad commit id')
|
||||
|
||||
prev_commit = seafserv_threaded_rpc.get_commit(current_commit.parent_id)
|
||||
if not prev_commit:
|
||||
return render_error('bad commit id')
|
||||
|
||||
path = path.encode('utf-8')
|
||||
|
||||
current_content, err = get_file_content_by_commit_and_path(request, \
|
||||
repo_id, current_commit.id, path)
|
||||
if err:
|
||||
return render_error(request, err)
|
||||
|
||||
prev_content, err = get_file_content_by_commit_and_path(request, \
|
||||
repo_id, prev_commit.id, path)
|
||||
if err:
|
||||
return render_error(request, err)
|
||||
|
||||
is_new_file = False
|
||||
diff_result_table = ''
|
||||
if prev_content == '' and current_content == '':
|
||||
is_new_file = True
|
||||
else:
|
||||
diff = HtmlDiff()
|
||||
diff_result_table = diff.make_table(prev_content.splitlines(),
|
||||
current_content.splitlines())
|
||||
|
||||
zipped = gen_path_link(path, repo.name)
|
||||
|
||||
return render_to_response('text_diff.html', {
|
||||
'repo': repo,
|
||||
'path': path,
|
||||
'zipped': zipped,
|
||||
'current_commit': current_commit,
|
||||
'prev_commit': prev_commit,
|
||||
'diff_result_table': diff_result_table,
|
||||
'is_new_file': is_new_file,
|
||||
}, context_instance=RequestContext(request))
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user