mirror of
https://github.com/haiwen/seahub.git
synced 2025-08-22 00:43:28 +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)
|
file_path_hash = models.CharField(max_length=12)
|
||||||
|
|
||||||
last_modified = models.BigIntegerField()
|
last_modified = models.BigIntegerField()
|
||||||
|
last_commit_id = models.CharField(max_length=40)
|
||||||
|
|
||||||
# email addresses seperated by comma
|
# email addresses seperated by comma
|
||||||
emails = models.TextField()
|
emails = models.TextField()
|
||||||
|
@ -928,7 +928,7 @@ ul.with-bg li {
|
|||||||
color:#666;
|
color:#666;
|
||||||
margin-left:5px;
|
margin-left:5px;
|
||||||
}
|
}
|
||||||
.lsch, .lsch-encrypted {
|
.lsch, .lsch-encrypted, .file-diff {
|
||||||
font-size:12px;
|
font-size:12px;
|
||||||
font-weight:normal;
|
font-weight:normal;
|
||||||
color:#666;
|
color:#666;
|
||||||
@ -1378,3 +1378,16 @@ ul.with-bg li {
|
|||||||
}
|
}
|
||||||
.placeholder { color: #aaa; }
|
.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 %}
|
{% if not view_history %}
|
||||||
<div id="file-commit-info">
|
<div id="file-commit-info">
|
||||||
<div class="latest-commit ovhd">
|
<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>
|
<a href="{{ SITE_ROOT }}repo/file_revisions/{{ repo.id }}/?p={{ path }}" class="more fright">更多历史</a>
|
||||||
</div>
|
</div>
|
||||||
<p class="contributors">
|
<p class="contributors">
|
||||||
|
@ -14,3 +14,6 @@
|
|||||||
{% if filetype == 'Markdown' %}
|
{% if filetype == 'Markdown' %}
|
||||||
<script type="text/javascript" src="{{MEDIA_URL}}js/showdown.js"></script>
|
<script type="text/javascript" src="{{MEDIA_URL}}js/showdown.js"></script>
|
||||||
{% endif %}
|
{% 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 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/upload_error/(?P<repo_id>[^/]+)/$', upload_file_error),
|
||||||
(r'^repo/update_error/(?P<repo_id>[^/]+)/$', update_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/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'),
|
url(r'^repo/(?P<repo_id>[^/]{36,36})/$', RepoView.as_view(), name='repo'),
|
||||||
(r'^repo/history/(?P<repo_id>[^/]+)/$', repo_history),
|
(r'^repo/history/(?P<repo_id>[^/]+)/$', repo_history),
|
||||||
(r'^repo/history/revert/(?P<repo_id>[^/]+)/$', repo_history_revert),
|
(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:
|
if user not in ret:
|
||||||
ret.append(user)
|
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):
|
def get_file_contributors(repo_id, file_path, file_path_hash, file_id):
|
||||||
"""Get file contributors list and last modfied time from database cache.
|
"""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 = []
|
contributors = []
|
||||||
last_modified = 0
|
last_modified = 0
|
||||||
|
last_commit_id = ''
|
||||||
try:
|
try:
|
||||||
fc = FileContributors.objects.get(repo_id=repo_id,
|
fc = FileContributors.objects.get(repo_id=repo_id,
|
||||||
file_path_hash=file_path_hash)
|
file_path_hash=file_path_hash)
|
||||||
except FileContributors.DoesNotExist:
|
except FileContributors.DoesNotExist:
|
||||||
# has no cache yet
|
# 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:
|
if not contributors:
|
||||||
return [], 0
|
return [], 0
|
||||||
emails = ','.join(contributors)
|
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=file_path,
|
||||||
file_path_hash=file_path_hash,
|
file_path_hash=file_path_hash,
|
||||||
last_modified=last_modified,
|
last_modified=last_modified,
|
||||||
|
last_commit_id=last_commit_id,
|
||||||
emails=emails)
|
emails=emails)
|
||||||
file_contributors.save()
|
file_contributors.save()
|
||||||
else:
|
else:
|
||||||
# cache found
|
# cache found
|
||||||
if fc.file_id != file_id:
|
if fc.file_id != file_id or not fc.last_commit_id:
|
||||||
# but cache is outdated
|
# but cache is outdated
|
||||||
fc.delete()
|
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:
|
if not contributors:
|
||||||
return [], 0
|
return [], 0, ''
|
||||||
emails = ','.join(contributors)
|
emails = ','.join(contributors)
|
||||||
file_contributors = FileContributors(repo_id=repo_id,
|
file_contributors = FileContributors(repo_id=repo_id,
|
||||||
file_id=file_id,
|
file_id=file_id,
|
||||||
file_path=file_path,
|
file_path=file_path,
|
||||||
file_path_hash=file_path_hash,
|
file_path_hash=file_path_hash,
|
||||||
last_modified=last_modified,
|
last_modified=last_modified,
|
||||||
|
last_commit_id=last_commit_id,
|
||||||
emails=emails)
|
emails=emails)
|
||||||
file_contributors.save()
|
file_contributors.save()
|
||||||
else:
|
else:
|
||||||
@ -415,8 +418,9 @@ def get_file_contributors(repo_id, file_path, file_path_hash, file_id):
|
|||||||
else:
|
else:
|
||||||
contributors = []
|
contributors = []
|
||||||
last_modified = fc.last_modified
|
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'):
|
if hasattr(settings, 'EVENTS_CONFIG_FILE'):
|
||||||
|
89
views.py
89
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_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, \
|
list_inner_pub_repos, get_org_groups_by_repo, is_org_repo_owner, \
|
||||||
get_org_repo_owner, is_passwd_set, get_file_size, \
|
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 pysearpc import SearpcError
|
||||||
|
|
||||||
from signals import repo_created, repo_deleted
|
from signals import repo_created, repo_deleted
|
||||||
@ -77,7 +77,6 @@ except ImportError:
|
|||||||
DOCUMENT_CONVERTOR_ROOT = None
|
DOCUMENT_CONVERTOR_ROOT = None
|
||||||
from settings import FILE_PREVIEW_MAX_SIZE, INIT_PASSWD
|
from settings import FILE_PREVIEW_MAX_SIZE, INIT_PASSWD
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def root(request):
|
def root(request):
|
||||||
return HttpResponseRedirect(reverse(myhome))
|
return HttpResponseRedirect(reverse(myhome))
|
||||||
@ -1290,7 +1289,7 @@ def repo_view_file(request, repo_id):
|
|||||||
file_path_hash = md5_constructor(urllib2.quote(path.encode('utf-8'))).hexdigest()[:12]
|
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)
|
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]
|
latest_contributor = contributors[0]
|
||||||
|
|
||||||
return render_to_response('repo_view_file.html', {
|
return render_to_response('repo_view_file.html', {
|
||||||
@ -1323,6 +1322,7 @@ def repo_view_file(request, repo_id):
|
|||||||
'contributors': contributors,
|
'contributors': contributors,
|
||||||
'latest_contributor': latest_contributor,
|
'latest_contributor': latest_contributor,
|
||||||
'last_modified': last_modified,
|
'last_modified': last_modified,
|
||||||
|
'last_commit_id': last_commit_id,
|
||||||
'read_only': read_only,
|
'read_only': read_only,
|
||||||
'page_from': page_from,
|
'page_from': page_from,
|
||||||
}, context_instance=RequestContext(request))
|
}, context_instance=RequestContext(request))
|
||||||
@ -2610,3 +2610,86 @@ def repo_set_password(request):
|
|||||||
|
|
||||||
return HttpResponse(json.dumps(ret),
|
return HttpResponse(json.dumps(ret),
|
||||||
content_type=content_type)
|
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