branch develop updated (570490d -> c8f2921)
This is an automated email from the git hooks/post-receive script. New change to branch develop in repository scmwebeditor. See http://git.nuiton.org/scmwebeditor.git from 570490d Make the autosave use the client's local storage instead of saving the file on the repository new c8f2921 Add the ability to view the edited file's history The 1 revisions listed above as "new" are entirely new to this repository and will be described in separate emails. The revisions listed as "adds" were already present in the repository and have only been added to this reference. Detailed log of new commits: commit c8f2921cc746b8a1ec4d030d314f6a881729e5c8 Author: Hugo PIGEON <hpigeon@codelutin.com> Date: Thu Jun 4 17:29:50 2015 +0200 Add the ability to view the edited file's history Summary of changes: .../org/nuiton/scmwebeditor/git/GitConnection.java | 160 +++++++++++++- .../org/nuiton/scmwebeditor/api/ScmConnection.java | 28 ++- .../org/nuiton/scmwebeditor/api/ScmRevision.java | 40 ++++ .../org/nuiton/scmwebeditor/svn/SvnConnection.java | 242 +++++++++++++++------ .../scmwebeditor/uiweb/actions/EditAction.java | 25 +++ ...ViewImageAction.java => ViewHistoryAction.java} | 121 +++-------- .../i18n/scmwebeditor-ui-web_en_GB.properties | 2 + .../i18n/scmwebeditor-ui-web_fr_FR.properties | 2 + swe-ui-web/src/main/resources/struts.xml | 4 + .../main/webapp/WEB-INF/content/imageViewer.jsp | 7 +- .../webapp/WEB-INF/content/modificationViewer.jsp | 46 +++- .../content/{preview.jsp => viewHistory.jsp} | 28 ++- swe-ui-web/src/main/webapp/css/main.css | 62 ++++-- 13 files changed, 569 insertions(+), 198 deletions(-) create mode 100644 swe-scm-api/src/main/java/org/nuiton/scmwebeditor/api/ScmRevision.java copy swe-ui-web/src/main/java/org/nuiton/scmwebeditor/uiweb/actions/{ViewImageAction.java => ViewHistoryAction.java} (57%) copy swe-ui-web/src/main/webapp/WEB-INF/content/{preview.jsp => viewHistory.jsp} (63%) -- To stop receiving notification emails like this one, please contact nuiton.org SCM administrator <admin+scm@nuiton.org>.
This is an automated email from the git hooks/post-receive script. New commit to branch develop in repository scmwebeditor. See http://git.nuiton.org/scmwebeditor.git commit c8f2921cc746b8a1ec4d030d314f6a881729e5c8 Author: Hugo PIGEON <hpigeon@codelutin.com> Date: Thu Jun 4 17:29:50 2015 +0200 Add the ability to view the edited file's history --- .../org/nuiton/scmwebeditor/git/GitConnection.java | 160 +++++++++++++- .../org/nuiton/scmwebeditor/api/ScmConnection.java | 28 ++- .../org/nuiton/scmwebeditor/api/ScmRevision.java | 40 ++++ .../org/nuiton/scmwebeditor/svn/SvnConnection.java | 242 +++++++++++++++------ .../scmwebeditor/uiweb/actions/EditAction.java | 25 +++ .../uiweb/actions/ViewHistoryAction.java | 201 +++++++++++++++++ .../i18n/scmwebeditor-ui-web_en_GB.properties | 2 + .../i18n/scmwebeditor-ui-web_fr_FR.properties | 2 + swe-ui-web/src/main/resources/struts.xml | 4 + .../main/webapp/WEB-INF/content/imageViewer.jsp | 7 +- .../webapp/WEB-INF/content/modificationViewer.jsp | 46 +++- .../main/webapp/WEB-INF/content/viewHistory.jsp | 44 ++++ swe-ui-web/src/main/webapp/css/main.css | 62 ++++-- 13 files changed, 760 insertions(+), 103 deletions(-) diff --git a/swe-git/src/main/java/org/nuiton/scmwebeditor/git/GitConnection.java b/swe-git/src/main/java/org/nuiton/scmwebeditor/git/GitConnection.java index 347a4f8..03eee50 100644 --- a/swe-git/src/main/java/org/nuiton/scmwebeditor/git/GitConnection.java +++ b/swe-git/src/main/java/org/nuiton/scmwebeditor/git/GitConnection.java @@ -29,6 +29,7 @@ import org.eclipse.jgit.api.*; import org.eclipse.jgit.api.errors.*; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectLoader; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevTree; @@ -37,8 +38,10 @@ import org.eclipse.jgit.storage.file.FileRepositoryBuilder; import org.eclipse.jgit.transport.CredentialsProvider; import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider; import org.eclipse.jgit.treewalk.TreeWalk; +import org.eclipse.jgit.treewalk.filter.PathFilter; import org.nuiton.scmwebeditor.api.RepositoryNotFoundException; import org.nuiton.scmwebeditor.api.ScmConnection; +import org.nuiton.scmwebeditor.api.ScmRevision; import org.nuiton.scmwebeditor.api.dto.BrowseDto; import org.nuiton.scmwebeditor.api.dto.CommitDto; import org.nuiton.scmwebeditor.api.dto.result.BrowseResultDto; @@ -46,15 +49,10 @@ import org.nuiton.scmwebeditor.api.dto.result.CommitResultDto; import org.nuiton.scmwebeditor.api.dto.result.RemoveFileResultDto; import javax.naming.AuthenticationException; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.UnsupportedEncodingException; +import java.io.*; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.LinkedList; +import java.util.*; /** * Implementation of the Git's main features @@ -521,6 +519,154 @@ public class GitConnection implements ScmConnection { return path; } + @Override + public Map<ScmRevision, String> getRevisions(String address, String username, String password) { + + final int MAX_MESSAGE_LENGTH = 32; + String pathOnRepo = address.replace(addressGit + "/", ""); + Map<ScmRevision, String> revisions = new TreeMap<ScmRevision, String>(); + + try { + updateRepository(username, password); + } catch (RepositoryNotFoundException e) { + + if (log.isErrorEnabled()) { + log.error("Error while cloning or pulling the repository", e); + } + return null; + + } catch (IOException e) { + + if (log.isErrorEnabled()) { + log.error("Error while cloning or pulling the repository", e); + } + return null; + + } catch (AuthenticationException e) { + + if (log.isErrorEnabled()) { + log.error("Error while cloning or pulling the repository", e); + } + return null; + } + + try { + // getting the revisions + Git git = Git.open(localDirectory); + + LogCommand logCmd = git.log(); + logCmd.all(); + logCmd.addPath(pathOnRepo); + + try { + Iterable<RevCommit> commits = logCmd.call(); + + // making the value and the key to put in the Map + for (RevCommit commit : commits) { + + // getting the commit message + String msg = commit.getShortMessage(); + + if (msg.length() > MAX_MESSAGE_LENGTH) { + msg = msg.substring(0, MAX_MESSAGE_LENGTH) + "..."; + } + + // getting the date + long commitTime = ((long) commit.getCommitTime()) * 1000; + + Date commitDate = new Date(commitTime); + msg += " - " + commitDate; + + // adding the revision to the Map + ScmRevision scmRev = new ScmRevision(commit.getName(), commitTime); + revisions.put(scmRev, msg); + } + + } catch (GitAPIException e) { + if (log.isErrorEnabled()) { + log.error("Can not get revisions for address " + address, e); + } + return null; + } + } catch (IOException e) { + if (log.isErrorEnabled()) { + log.error("Can not open git local repository : " + localDirectory.getAbsolutePath(), e); + } + return null; + } + + return revisions; + } + + @Override + public File getFileContentAtRevision(String path, String username, String password, + String revision) throws AuthenticationException { + + try { + updateRepository(username, password); + } catch (RepositoryNotFoundException e) { + + if (log.isErrorEnabled()) { + log.error("Error while cloning or pulling the repository", e); + } + + } catch (IOException e) { + + if (log.isErrorEnabled()) { + log.error("Error while cloning or pulling the repository", e); + } + + } catch (AuthenticationException e) { + + if (log.isErrorEnabled()) { + log.error("Error while cloning or pulling the repository", e); + } + } + + String pathOnRepo = path.replace(addressGit + "/", ""); + + String tempFileName = localDirectory.getAbsolutePath() + File.separator + pathOnRepo + "_" + revision; + File tempFile = new File(tempFileName); + + ObjectId lastCommitId; + + try { + + lastCommitId = gitRepo.resolve(revision); + + // a RevWalk allows to walk over commits based on some filtering that is defined + RevWalk revWalk = new RevWalk(gitRepo); + RevCommit commit = revWalk.parseCommit(lastCommitId); + // and using commit's tree find the path + RevTree tree = commit.getTree(); + + // now try to find a specific file + TreeWalk treeWalk = new TreeWalk(gitRepo); + treeWalk.addTree(tree); + treeWalk.setRecursive(true); + treeWalk.setFilter(PathFilter.create(pathOnRepo)); + if (!treeWalk.next()) { + throw new IllegalStateException("Did not find expected file '" + pathOnRepo + "'"); + } + + ObjectId objectId = treeWalk.getObjectId(0); + ObjectLoader loader = gitRepo.open(objectId); + + // and then one can the loader to read the file + FileOutputStream fos = new FileOutputStream(tempFile); + loader.copyTo(fos); + + revWalk.dispose(); + + } catch (IOException e) { + if (log.isErrorEnabled()) { + log.error("Error while getting file '" + pathOnRepo + "' content at revision " + revision, e); + } + } + + return tempFile; + } + /** * Changing for another branch diff --git a/swe-scm-api/src/main/java/org/nuiton/scmwebeditor/api/ScmConnection.java b/swe-scm-api/src/main/java/org/nuiton/scmwebeditor/api/ScmConnection.java index b0f23f5..6e70f7a 100644 --- a/swe-scm-api/src/main/java/org/nuiton/scmwebeditor/api/ScmConnection.java +++ b/swe-scm-api/src/main/java/org/nuiton/scmwebeditor/api/ScmConnection.java @@ -26,6 +26,8 @@ import org.nuiton.scmwebeditor.api.dto.result.*; import javax.naming.AuthenticationException; import java.io.File; +import java.util.List; +import java.util.Map; /** * An interface which gives the SCMs main features @@ -49,7 +51,7 @@ public interface ScmConnection { /** - * Gives the content of a file as a String + * Gives the content of a file * @param path the path to the file to get the content from * @param username the user's login for the SCM * @param password the user's password for the SCM @@ -91,4 +93,28 @@ public interface ScmConnection { * @return the path to use to display an image on a web page */ String getImagePath(String address, String repositoryRoot); + + + /** + * Gives a list of the important revisions for the file at the given address + * @param address the file's address + * @param username the username to use to connect to the repository + * @param password the password to use to connect to the repository + * @return a list of revisions for the file at the given address with their revision name + * @throws AuthenticationException if there is a problem during the authentication process + */ + Map<ScmRevision, String> getRevisions(String address, String username, String password) throws AuthenticationException; + + + /** + * Gives the content of a file at the specified revision + * @param path the path to the file to get the content from + * @param username the user's login for the SCM + * @param password the user's password for the SCM + * @param revision the revision ID to use to get the file content + * @return a String which contains the file's content + * @throws AuthenticationException if there is a problem during the authentication process + */ + File getFileContentAtRevision(String path, String username, String password, String revision) + throws AuthenticationException; } diff --git a/swe-scm-api/src/main/java/org/nuiton/scmwebeditor/api/ScmRevision.java b/swe-scm-api/src/main/java/org/nuiton/scmwebeditor/api/ScmRevision.java new file mode 100644 index 0000000..fa78009 --- /dev/null +++ b/swe-scm-api/src/main/java/org/nuiton/scmwebeditor/api/ScmRevision.java @@ -0,0 +1,40 @@ +package org.nuiton.scmwebeditor.api; + +public class ScmRevision implements Comparable { + + /** the revision identifier (or revision number) */ + protected String revisionId; + + /** the commit time for this revision, in milliseconds */ + protected long commitTime; + + + public String getRevisionId() { return revisionId; } + + public void setRevisionId(String revisionId) { this.revisionId = revisionId; } + + public long getCommitTime() { return commitTime; } + + public void setCommitTime(long commitTime) { this.commitTime = commitTime; } + + + public ScmRevision(String revisionId, long commitTime) { + this.revisionId = revisionId; + this.commitTime = commitTime; + } + + @Override + public String toString() { + return revisionId; + } + + @Override + public int compareTo(Object o) { + + int returnVal = Long.compare(commitTime, ((ScmRevision) o).getCommitTime()); + + returnVal *= -1; // to get the revisions from the last one to the oldest one + + return returnVal; + } +} diff --git a/swe-svn/src/main/java/org/nuiton/scmwebeditor/svn/SvnConnection.java b/swe-svn/src/main/java/org/nuiton/scmwebeditor/svn/SvnConnection.java index c9a1e7a..baa9116 100644 --- a/swe-svn/src/main/java/org/nuiton/scmwebeditor/svn/SvnConnection.java +++ b/swe-svn/src/main/java/org/nuiton/scmwebeditor/svn/SvnConnection.java @@ -25,6 +25,7 @@ import org.apache.commons.io.FileUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.nuiton.scmwebeditor.api.ScmConnection; +import org.nuiton.scmwebeditor.api.ScmRevision; import org.nuiton.scmwebeditor.api.dto.BrowseDto; import org.nuiton.scmwebeditor.api.dto.CommitDto; import org.nuiton.scmwebeditor.api.dto.result.BrowseResultDto; @@ -35,13 +36,20 @@ import org.tmatesoft.svn.core.auth.ISVNAuthenticationManager; import org.tmatesoft.svn.core.internal.io.dav.DAVRepositoryFactory; import org.tmatesoft.svn.core.internal.util.SVNEncodingUtil; import org.tmatesoft.svn.core.internal.wc.DefaultSVNOptions; +import org.tmatesoft.svn.core.io.SVNFileRevision; import org.tmatesoft.svn.core.io.SVNRepository; import org.tmatesoft.svn.core.io.SVNRepositoryFactory; import org.tmatesoft.svn.core.wc.*; import javax.naming.AuthenticationException; import java.io.*; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; import java.util.Collection; +import java.util.Date; +import java.util.Map; +import java.util.TreeMap; /** * Implementation of the SVN's main features @@ -428,9 +436,167 @@ public class SvnConnection implements ScmConnection { @Override public File getFileContent(String path, String username, String password) throws AuthenticationException { + File fileContent = getFileContentAtRevision(path, username, password, "-1"); + + return fileContent; + } + + + @Override + public String getHeadRevisionNumber(String path, String username, String password) throws AuthenticationException { + + if (log.isDebugEnabled()) { + log.debug("headRevisionNumber expected " + addressSvn + " ; got " + path); + } + + ISVNAuthenticationManager svnAuthManager = SVNWCUtil.createDefaultAuthenticationManager(username, password); + + DefaultSVNOptions svnOptions = new DefaultSVNOptions(); + svnOptions.setPropertyValue(SVNProperty.EOL_STYLE, SVNProperty.EOL_STYLE_LF); + + SVNWCClient wcClient = new SVNWCClient(svnAuthManager, svnOptions); + + SVNInfo info = null; + + try { + info = wcClient.doInfo(SVNURL.parseURIEncoded(path), SVNRevision.HEAD, SVNRevision.HEAD); + } catch (SVNAuthenticationException e) { + throw new AuthenticationException("Auth fail"); + } catch (SVNException e) { + if (log.isErrorEnabled()) { + log.error("Can not get info from SVN repository", e); + } + } + + String headRevision = ""; + + if (info != null) { + headRevision = info.getRevision().toString(); + } + + return headRevision; + } + + + @Override + public String getRepositoryId() { + String repositoryUUID; + try { + String encodedUrl = SVNEncodingUtil.autoURIEncode(addressSvn); + SVNRepository repository = SVNRepositoryFactory.create(SVNURL.parseURIEncoded(encodedUrl)); + ISVNAuthenticationManager svnAuthManager = SVNWCUtil.createDefaultAuthenticationManager(); + repository.setAuthenticationManager(svnAuthManager); + + repositoryUUID = repository.getRepositoryUUID(true); + } catch (SVNException e) { + if (log.isDebugEnabled()) { + log.debug("Can't get UUID", e); + } + return null; + } + + return repositoryUUID; + } + + + @Override + public String getFileName() { + return fileName; + } + + @Override + public String getImagePath(String address, String repositoryRoot) { + return address; + } + + @Override + public Map<ScmRevision, String> getRevisions(String address, String username, String password) throws AuthenticationException { + + final int MAX_MESSAGE_LENGTH = 32; + + String url = address.substring(0, address.lastIndexOf('/')); + String file = address.substring(address.lastIndexOf('/') + 1); + + Map<ScmRevision, String> revisions = new TreeMap<ScmRevision, String>(); + + updateAuthentication(username, password); + + SVNRepository repository; + + try { + // getting the revisions + repository = SVNRepositoryFactory.create(SVNURL.parseURIEncoded(url)); + + ISVNAuthenticationManager svnAuthManager = SVNWCUtil.createDefaultAuthenticationManager(username, password); + repository.setAuthenticationManager(svnAuthManager); + + SVNNodeKind nodeKind = repository.checkPath(file, -1); + + if (nodeKind == SVNNodeKind.NONE) { + if (log.isErrorEnabled()) { + log.error("There is no entry at '" + address + "'."); + } + throw new IllegalArgumentException("There is no entry at '" + address + "'."); + } else if (nodeKind == SVNNodeKind.DIR) { + if (log.isErrorEnabled()) { + log.error("The entry at '" + address + "' is a file while a directory was expected."); + } + throw new IllegalArgumentException("The entry at '" + address + "' is a directory while a file was expected."); + } + + Collection fileRevisions; + fileRevisions = repository.getFileRevisions(file, null, 0, repository.getLatestRevision()); + + // making the value and the key to put in the Map + for (Object fileRev : fileRevisions) { + + // getting the commit message + String msg = ((SVNFileRevision) fileRev).getRevisionProperties().getStringValue(SVNRevisionProperty.LOG); + if (msg.length() > MAX_MESSAGE_LENGTH) { + msg = msg.substring(0, MAX_MESSAGE_LENGTH) + "..."; + } + + long revNum = ((SVNFileRevision) fileRev).getRevision(); + msg += " (rev " + revNum + ")"; + + // getting the date + String date = ((SVNFileRevision) fileRev).getRevisionProperties().getStringValue(SVNRevisionProperty.DATE); + DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSSSS'Z'"); + Date commitDate = null; + try { + commitDate = df.parse(date); + } catch (ParseException e) { + if (log.isErrorEnabled()) { + log.error("Can not parse date " + date); + } + } + msg += " - " + commitDate; + + // adding the revision to the Map + ScmRevision scmRev = new ScmRevision(String.valueOf(revNum), commitDate.getTime()); + revisions.put(scmRev, msg); + } + + } catch (SVNAuthenticationException e) { + throw new AuthenticationException("Auth fail"); + } catch (SVNException e) { + if (log.isErrorEnabled()) { + log.error("Can not get revisions from SVN repository for address " + address, e); + } + } + + return revisions; + } + + @Override + public File getFileContentAtRevision(String path, String username, String password, + String revision) throws AuthenticationException { + String url = path.substring(0, path.lastIndexOf('/')); String file = path.substring(path.lastIndexOf('/') + 1); + long rev = Long.parseLong(revision); + // storing the file content to the user's local directory File localDirectory = new File(pathToLocalRepos); @@ -461,19 +627,19 @@ public class SvnConnection implements ScmConnection { ISVNAuthenticationManager svnAuthManager = SVNWCUtil.createDefaultAuthenticationManager(username, password); repository.setAuthenticationManager(svnAuthManager); - SVNNodeKind nodeKind = repository.checkPath(file, -1); + SVNNodeKind nodeKind = repository.checkPath(file, rev); if (nodeKind == SVNNodeKind.NONE) { if (log.isErrorEnabled()) { log.error("There is no entry at '" + url + "'."); } - throw new IllegalArgumentException("There is no entry at '" + url + "'."); + return null; } else if (nodeKind == SVNNodeKind.DIR) { if (log.isErrorEnabled()) { log.error("The entry at '" + url + "' is a file while a directory was expected."); } - throw new IllegalArgumentException("The entry at '" + url + "' is a file while a directory was expected."); + return null; } @@ -481,7 +647,7 @@ public class SvnConnection implements ScmConnection { ByteArrayOutputStream baos = new ByteArrayOutputStream(); SVNProperties fileProperties = new SVNProperties(); - repository.getFile(file, -1, fileProperties, baos); + repository.getFile(file, rev, fileProperties, baos); fileProperties.getStringValue(SVNProperty.REVISION); try { @@ -517,74 +683,6 @@ public class SvnConnection implements ScmConnection { } - @Override - public String getHeadRevisionNumber(String path, String username, String password) throws AuthenticationException { - - if (log.isDebugEnabled()) { - log.debug("headRevisionNumber expected " + addressSvn + " ; got " + path); - } - - ISVNAuthenticationManager svnAuthManager = SVNWCUtil.createDefaultAuthenticationManager(username, password); - - DefaultSVNOptions svnOptions = new DefaultSVNOptions(); - svnOptions.setPropertyValue(SVNProperty.EOL_STYLE, SVNProperty.EOL_STYLE_LF); - - SVNWCClient wcClient = new SVNWCClient(svnAuthManager, svnOptions); - - SVNInfo info = null; - - try { - info = wcClient.doInfo(SVNURL.parseURIEncoded(path), SVNRevision.HEAD, SVNRevision.HEAD); - } catch (SVNAuthenticationException e) { - throw new AuthenticationException("Auth fail"); - } catch (SVNException e) { - if (log.isErrorEnabled()) { - log.error("Can not get info from SVN repository", e); - } - } - - String headRevision = ""; - - if (info != null) { - headRevision = info.getRevision().toString(); - } - - return headRevision; - } - - - @Override - public String getRepositoryId() { - String repositoryUUID; - try { - String encodedUrl = SVNEncodingUtil.autoURIEncode(addressSvn); - SVNRepository repository = SVNRepositoryFactory.create(SVNURL.parseURIEncoded(encodedUrl)); - ISVNAuthenticationManager svnAuthManager = SVNWCUtil.createDefaultAuthenticationManager(); - repository.setAuthenticationManager(svnAuthManager); - - repositoryUUID = repository.getRepositoryUUID(true); - } catch (SVNException e) { - if (log.isDebugEnabled()) { - log.debug("Can't get UUID", e); - } - return null; - } - - return repositoryUUID; - } - - - @Override - public String getFileName() { - return fileName; - } - - @Override - public String getImagePath(String address, String repositoryRoot) { - return address; - } - - public String getSvnRoot(String username, String password) { String repositoryRoot; try { diff --git a/swe-ui-web/src/main/java/org/nuiton/scmwebeditor/uiweb/actions/EditAction.java b/swe-ui-web/src/main/java/org/nuiton/scmwebeditor/uiweb/actions/EditAction.java index 9f50b38..94d07f6 100644 --- a/swe-ui-web/src/main/java/org/nuiton/scmwebeditor/uiweb/actions/EditAction.java +++ b/swe-ui-web/src/main/java/org/nuiton/scmwebeditor/uiweb/actions/EditAction.java @@ -29,6 +29,7 @@ import org.apache.shiro.crypto.BlowfishCipherService; import org.nuiton.scmwebeditor.api.OperationNotSupportedException; import org.nuiton.scmwebeditor.api.ScmConnection; import org.nuiton.scmwebeditor.api.ScmProvider; +import org.nuiton.scmwebeditor.api.ScmRevision; import org.nuiton.scmwebeditor.api.dto.result.AbstractResultDto; import org.nuiton.scmwebeditor.uiweb.ScmWebEditorConfig; @@ -40,6 +41,7 @@ import java.io.IOException; import java.io.UnsupportedEncodingException; import java.text.Normalizer; import java.util.LinkedList; +import java.util.Map; /** * Allows to edit a file @@ -66,6 +68,12 @@ public class EditAction extends ScmWebEditorMainAction { /** the interval between two automatic saves */ protected int autoSaveInterval; + /** a list of the revision which can be used to view the file's history */ + protected Map<ScmRevision, String> revisions; + + /** the selected revision to view */ + protected String revision; + public String getSelectedBranch() { return selectedBranch; } @@ -91,6 +99,15 @@ public class EditAction extends ScmWebEditorMainAction { public void setAutoSaveInterval(int autoSaveInterval) { this.autoSaveInterval = autoSaveInterval; } + public Map<ScmRevision, String> getRevisions() { return revisions; } + + public void setRevisions( + Map<ScmRevision, String> revisions) { this.revisions = revisions; } + + public String getRevision() { return revision; } + + public void setRevision(String revision) { this.revision = revision; } + /** * Execution of the edit action * @return a code interpreted in the file struts.xml @@ -225,6 +242,14 @@ public class EditAction extends ScmWebEditorMainAction { } } + try { + revisions = scmConn.getRevisions(address, username, pw); + } catch (AuthenticationException e) { + if (log.isErrorEnabled()) { + log.error("Can not get revisions for address " + address + " : authentication error", e); + } + } + /* * Getting the file and its revision diff --git a/swe-ui-web/src/main/java/org/nuiton/scmwebeditor/uiweb/actions/ViewHistoryAction.java b/swe-ui-web/src/main/java/org/nuiton/scmwebeditor/uiweb/actions/ViewHistoryAction.java new file mode 100644 index 0000000..7eb17a6 --- /dev/null +++ b/swe-ui-web/src/main/java/org/nuiton/scmwebeditor/uiweb/actions/ViewHistoryAction.java @@ -0,0 +1,201 @@ +/* + * #%L + * ScmWebEditor + * %% + * Copyright (C) 2009 - 2015 CodeLutin + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Lesser Public License for more details. + * + * You should have received a copy of the GNU General Lesser Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/lgpl-3.0.html>. + * #L% + */ +package org.nuiton.scmwebeditor.uiweb.actions; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.shiro.codec.Base64; +import org.apache.shiro.crypto.BlowfishCipherService; +import org.nuiton.scmwebeditor.api.ScmConnection; +import org.nuiton.scmwebeditor.api.ScmProvider; +import org.nuiton.scmwebeditor.uiweb.ScmWebEditorConfig; + +import javax.naming.AuthenticationException; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpSession; +import java.io.File; +import java.io.IOException; +import java.text.Normalizer; + +/** + * Allows to view the history of a file + */ +public class ViewHistoryAction extends ScmWebEditorMainAction { + + private static final Log log = LogFactory.getLog(ViewHistoryAction.class); + + /** the content of the file */ + protected String fileContent; + + /** the revision to use to get the file content */ + protected String revision; + + /** the error name or null if no error occurred */ + protected String error; + + + public String getFileContent() { return fileContent; } + + public void setFileContent(String fileContent) { this.fileContent = fileContent; } + + public String getRevision() { return revision; } + + public void setRevision(String revision) { this.revision = revision; } + + public String getError() { return error; } + + public void setError(String error) { this.error = error; } + + /** + * Execution of the view history action + * @return a code interpreted in the file struts.xml + */ + public String execute() { + + HttpSession session = request.getSession(); + String sessionId = session.getId(); + + error = null; + + String pathToLocalRepos = ScmWebEditorConfig.getLocalRepositoriesPath() + File.separator + sessionId; + + ScmProvider provider = ScmWebEditorConfig.getProvider(scmType); + ScmConnection scmConn = provider.getConnection(address, pathToLocalRepos); + + // if the repository is not protected, we get its UUID + String repositoryUUID = scmConn.getRepositoryId(); + if (repositoryUUID == null) { + repositoryUUID = address.replace(' ', '_'); + repositoryUUID = Normalizer.normalize(repositoryUUID, Normalizer.Form.NFD).replaceAll("[\u0300-\u036F]", ""); + } + + if (log.isDebugEnabled()) { + log.debug("Login : " + username); + } + + + /* + * Reading the cookie + */ + + + String usernamepwCookie = null; + // read the cookies + + BlowfishCipherService bf = new BlowfishCipherService(); + + byte[] privateKey = Base64.decode(ScmWebEditorConfig.getKey()); + + if (request.getCookies() != null) { + for (Cookie c : request.getCookies()) { + if (c.getName().equals(repositoryUUID)) { + usernamepwCookie = c.getValue(); + } + } + } + + if (usernamepwCookie != null) { + + String usernameDecode = new String(bf.decrypt(Base64.decode(usernamepwCookie), privateKey).getBytes()); + + String[] resCookie = usernameDecode.split(","); + if (resCookie.length == 2) { + username = resCookie[0]; + pw = resCookie[1]; + } + } + + if (saveCookie) { + if (username != null && pw != null) { + + if (!username.equals("") && !pw.equals("")) { + Cookie authCookie = new Cookie(repositoryUUID, bf.encrypt((username + "," + pw).getBytes(), privateKey).toBase64()); + authCookie.setMaxAge(60 * 60 * 24 * 365); + response.addCookie(authCookie); + } + } + + } + + // authentication + String[] usernamePw = getUsernamePwFromSession(repositoryUUID, username, pw); + username = usernamePw[0]; + pw = usernamePw[1]; + + String name = username; + String password = pw; + + if (name == null) { + name = "anonymous"; + } + if (password == null) { + password = "anonymous"; + } + + + /* + * Getting the file's revision + */ + + try { + File tempFile = scmConn.getFileContentAtRevision(address, name, password, revision); + + if (tempFile != null) { + fileContent = FileUtils.readFileToString(tempFile); + } else { + error = ERROR; + return ERROR; + } + } catch (AuthenticationException e) { + request.setAttribute(PARAMETER_ADDRESS, address); + + // if scm authentication failed user is redirected on login page + if (log.isDebugEnabled()) { + log.debug("Auth Fail ", e); + } + + // deleting the cookies for this repository + for (Cookie c : request.getCookies()) { + if (c.getName().equals(repositoryUUID)) { + c.setMaxAge(0);//On supprime le cookie + response.addCookie(c); + if (log.isDebugEnabled()) { + log.debug("Cookie supprimé"); + } + } + } + + getScmSession().delScmUser(repositoryUUID); + error = LOGIN; + return LOGIN; + } catch (IOException e) { + if (log.isErrorEnabled()) { + log.error("Can not read temp file for " + address + " at revision " + revision, e); + } + error = ERROR; + return ERROR; + } + + return SUCCESS; + } +} diff --git a/swe-ui-web/src/main/resources/i18n/scmwebeditor-ui-web_en_GB.properties b/swe-ui-web/src/main/resources/i18n/scmwebeditor-ui-web_en_GB.properties index 6af700a..ac8b2db 100644 --- a/swe-ui-web/src/main/resources/i18n/scmwebeditor-ui-web_en_GB.properties +++ b/swe-ui-web/src/main/resources/i18n/scmwebeditor-ui-web_en_GB.properties @@ -27,6 +27,7 @@ scm.exitJavascript=Exit ScmWebEditor? scm.exitJavascriptChanges=Exit ScmWebEditor without saving? All modification will be lost. scm.exitTitle=Exit ScmWebEditor without saving. scm.fileModify=File modify while editing. +scm.fileRevision=File revision\: scm.fileToMove=File to move\: scm.forceSave=Force save scm.formTransferred=You should be transferred automatically to the form page. If not please @@ -108,5 +109,6 @@ scm.uploadTitle=Upload a file on the repository scm.uselessSave=It's useless to save the file, file is not modify. scm.username=Username scm.usernameTitle=Repository username +scm.viewHistory=View file history scm.welcome=Welcome on SCMWebEditor scm.yes=Yes diff --git a/swe-ui-web/src/main/resources/i18n/scmwebeditor-ui-web_fr_FR.properties b/swe-ui-web/src/main/resources/i18n/scmwebeditor-ui-web_fr_FR.properties index ed26f30..839afba 100644 --- a/swe-ui-web/src/main/resources/i18n/scmwebeditor-ui-web_fr_FR.properties +++ b/swe-ui-web/src/main/resources/i18n/scmwebeditor-ui-web_fr_FR.properties @@ -27,6 +27,7 @@ scm.exitJavascript=Quitter ScmWebEditor ? scm.exitJavascriptChanges=Quitter ScmWebEditor sans sauvegarder ? Toutes les modifications seront perdues. scm.exitTitle=Quitter ScmWebEditor sans sauvegarder. scm.fileModify=Fichier modifié pendant l'édition. +scm.fileRevision=Révision du fichier \: scm.fileToMove=Fichier à déplacer \: scm.forceSave=Forcer la sauvegarde scm.formTransferred=Vous devriez être redirigé vers la page du formulaire. si non @@ -108,5 +109,6 @@ scm.uploadTitle=Ajouter un fichier sur le dépôt scm.uselessSave=Inutile de sauvegarder le fichier, aucune modification n'a été apportée scm.username=Identifiant scm.usernameTitle=Identifiant du dépôt +scm.viewHistory=Voir l'historique du fichier scm.welcome=Bienvenue sur SCMWebEditor scm.yes=Oui diff --git a/swe-ui-web/src/main/resources/struts.xml b/swe-ui-web/src/main/resources/struts.xml index ec640c5..783b0e7 100644 --- a/swe-ui-web/src/main/resources/struts.xml +++ b/swe-ui-web/src/main/resources/struts.xml @@ -155,6 +155,10 @@ <result name="login">/WEB-INF/content/popups/createBranchForm.jsp</result> <result name="error">/WEB-INF/content/popups/createBranchForm.jsp</result> </action> + + <action name="viewHistory" class="org.nuiton.scmwebeditor.uiweb.actions.ViewHistoryAction"> + <result name="*">/WEB-INF/content/viewHistory.jsp</result> + </action> <action name="preview" class="org.nuiton.scmwebeditor.uiweb.actions.PreviewAction"> <result>/WEB-INF/content/preview.jsp</result> diff --git a/swe-ui-web/src/main/webapp/WEB-INF/content/imageViewer.jsp b/swe-ui-web/src/main/webapp/WEB-INF/content/imageViewer.jsp index 445f6da..4b731ca 100644 --- a/swe-ui-web/src/main/webapp/WEB-INF/content/imageViewer.jsp +++ b/swe-ui-web/src/main/webapp/WEB-INF/content/imageViewer.jsp @@ -129,7 +129,7 @@ <s:text name="scm.openAnotherFile"/> </s:set> -<s:submit id="openAnotherFile" value="%{openAnotherFile}" onclick="openPopin('openFilePopin'); return false;"/> +<s:submit id="openAnotherFileImg" value="%{openAnotherFile}" onclick="openPopin('openFilePopin'); return false;"/> <div id="buttonList"> @@ -203,7 +203,7 @@ <s:property value="address"/> </s:set> - <ul id="repositoryButtons"> + <ul id="repositoryButtonsImg"> <li> <s:submit name="uploadButton" value="%{scm.upload}" title="%{scm.uploadTitle}" onClick="javascript:open_popup('doUploadFile.action', 'upload', getElementById('address').value, '%{scmType}'); return false;"/> @@ -275,9 +275,6 @@ </div> </div> - <div id="targetContentUpload"></div> - - </div> <p align="right">©2004-2015 CodeLutin</p> </div> diff --git a/swe-ui-web/src/main/webapp/WEB-INF/content/modificationViewer.jsp b/swe-ui-web/src/main/webapp/WEB-INF/content/modificationViewer.jsp index 757b788..6c962c5 100644 --- a/swe-ui-web/src/main/webapp/WEB-INF/content/modificationViewer.jsp +++ b/swe-ui-web/src/main/webapp/WEB-INF/content/modificationViewer.jsp @@ -206,6 +206,12 @@ <s:submit id="openAnotherFile" value="%{openAnotherFile}" onclick="openPopin('openFilePopin'); return false;"/> +<s:set id="viewHistory"> + <s:text name="scm.viewHistory"/> +</s:set> + +<s:submit id="viewHistory" value="%{viewHistory}" onclick="openPopin('fileHistoryPopin'); return false;"/> + <div id="buttonList"> <!--BEGIN Save and continue --> @@ -483,9 +489,6 @@ </div> </form> - <div id="targetContentUpload"></div> - - </div> <p align="right">©2004-2015 CodeLutin</p> </div> @@ -589,6 +592,43 @@ </div> +<!-- popin to view the file history --> +<div class="popin" id="fileHistoryPopin"> + <span class="closePopin" onclick="closePopin('fileHistoryPopin')"> + X + </span> + + <h1><s:text name="scm.viewHistory"/></h1> + + <s:url id="ajaxViewHistory" value="viewHistory.action"/> + + <form id="viewHistoryForm"> + + <label for="revisionsList"><s:text name="scm.fileRevision"/></label><br/> + <s:select id="revisionsList" name="revision" list="revisions"/><br/> + + <sj:a + id="ajaxHistoryButton" + formIds="editForm,viewHistoryForm" + targets="htmlContentHistory" + href="%{ajaxViewHistory}" + title="%{scm.viewHistory}" + > + + <s:submit id="viewHistoryButton" value="%{scm.submit}" + onclick="document.getElementById('historyIndicator').style.display = 'inline'; return false;"/> + + <img src="struts/js/jstree/themes/classic/throbber.gif" alt="loading" class="indicator" id="historyIndicator"/> + + </sj:a> + + </form> + + <div id="htmlContentHistory"></div> + +</div> + + <div id="popinBackground"></div> <s:if test="autoSaveEnabled"> diff --git a/swe-ui-web/src/main/webapp/WEB-INF/content/viewHistory.jsp b/swe-ui-web/src/main/webapp/WEB-INF/content/viewHistory.jsp new file mode 100644 index 0000000..fab1295 --- /dev/null +++ b/swe-ui-web/src/main/webapp/WEB-INF/content/viewHistory.jsp @@ -0,0 +1,44 @@ +<%-- + #%L + ScmWebEditor + %% + Copyright (C) 2009 - 2015 CodeLutin + %% + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Lesser Public License for more details. + + You should have received a copy of the GNU General Lesser Public + License along with this program. If not, see + <http://www.gnu.org/licenses/lgpl-3.0.html>. + #L% + --%> + +<%@ page language="java" contentType="text/html; charset=UTF-8" + pageEncoding="UTF-8" %> + +<%@ taglib prefix="s" uri="/struts-tags" %> + +<script type="text/javascript"> + document.getElementById("historyIndicator").style.display = 'none'; +</script> + +<s:if test="error == null"> + +<pre id="historyContent"> +<s:property value="fileContent"/> +</pre> + +</s:if> +<s:elseif test="error.equals('login')"> + <s:text name="scm.badUsernameOrPassword"/> +</s:elseif> +<s:else> + <s:text name="scm.repoError"/> +</s:else> diff --git a/swe-ui-web/src/main/webapp/css/main.css b/swe-ui-web/src/main/webapp/css/main.css index 613fe13..4c3053f 100644 --- a/swe-ui-web/src/main/webapp/css/main.css +++ b/swe-ui-web/src/main/webapp/css/main.css @@ -225,8 +225,8 @@ ul.flags li { width:200px; height:32px; position:relative; - bottom:20px; - left: 70%; + bottom:30px; + left: 72%; } @@ -384,7 +384,7 @@ ul.flags li { } #preview { - overflow: scroll; + overflow: auto; height: 568px; } @@ -421,14 +421,15 @@ ul.flags li { height: 75%; top: 12%; left: 25%; - overflow: scroll; + overflow: auto; } -#autoSavePopin { - height: 75%; - top: 12%; - left: 25%; - overflow: scroll; +#fileHistoryPopin { + height: 90%; + top: 4%; + left: 4%; + overflow: auto; + width: 90%; } #popinBackground { @@ -451,7 +452,7 @@ ul.flags li { color: red; } -#ajaxSaveButton, #ajaxAutoSaveButton { +#ajaxSaveButton, #ajaxHistoryButton { text-decoration: none; } @@ -459,25 +460,41 @@ ul.flags li { display: none; } -#openAnotherFile { +#openAnotherFile, #openAnotherFileImg { position: relative; top: 20px; + right: 10%; +} + +#openAnotherFileImg { + right: 0; } -#wwctrl_openAnotherFile { +#wwctrl_openAnotherFile, #wwctrl_openAnotherFileImg, #wwctrl_viewHistory { text-align: center; } -ul#repositoryButtons { +#viewHistory { + position: relative; + bottom: 5px; + left: 10%; +} + +ul#repositoryButtons, ul#repositoryButtonsImg { display: table; list-style: none; border-spacing: 10px; position: relative; - bottom: 10px; + bottom: 30px; margin: auto; } -ul#repositoryButtons li { +ul#repositoryButtonsImg { + bottom: 10px; + left: 5%; +} + +ul#repositoryButtons li, ul#repositoryButtonsImg li { display: table-cell; } @@ -488,4 +505,19 @@ ul#repositoryButtons li { #displayedImage { display: block; margin: auto; +} + +#historyContent { + border: 1px solid black; + overflow: auto; + min-height: 150px; + padding: 5px; +} + +#wwctrl_viewHistoryButton { + display: inline; +} + +#viewHistoryButton { + margin-top: 10px; } \ No newline at end of file -- To stop receiving notification emails like this one, please contact nuiton.org SCM administrator <admin+scm@nuiton.org>.
participants (1)
-
nuiton.org scm