/************************************************************************** OmegaT - Computer Assisted Translation (CAT) tool with fuzzy matching, translation memory, keyword search, glossaries, and translation leveraging into updated projects. Copyright (C) 2014 Alex Buloichik, Martin Fleurke, Aaron Madlon-Kay Home page: http://www.omegat.org/ Support center: http://groups.yahoo.com/group/OmegaT/ This file is part of OmegaT. OmegaT is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. OmegaT 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 Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. **************************************************************************/ package org.omegat.core.team2; import java.io.File; import java.util.logging.Logger; import org.apache.commons.io.FileUtils; import org.apache.commons.lang.StringUtils; import org.omegat.util.Log; /** * Core for rebase and commit files. * * @author Alex Buloichik (alex73mail@gmail.com) * @author Martin Fleurke * @author Aaron Madlon-Kay */ public class RebaseAndCommit { private static final Logger LOGGER = Logger.getLogger(RebaseAndCommit.class.getName()); public static final String VERSION_PREFIX = "version-based-on."; /** * Load BASE and HEAD from remote repository into temp storage for future rebase. */ public static Prepared prepare(RemoteRepositoryProvider provider, File projectDir, String path) throws Exception { if (!provider.isUnderMapping(path)) { throw new RuntimeException("Path is not under mapping: " + path); } final String currentBaseVersion; String savedVersion = provider.getTeamSettings().get(VERSION_PREFIX + path); if (savedVersion == null) { return null; } Prepared r = new Prepared(); r.path = path; currentBaseVersion = savedVersion; Log.logDebug(LOGGER, "Retrieve BASE(" + currentBaseVersion + ") version of '" + path + "'"); // retrieve BASE version File baseFile = provider.switchToVersion(path, currentBaseVersion); // save it to prepared dir r.versionBase = currentBaseVersion; r.fileBase = provider.toPrepared(baseFile); Log.logDebug(LOGGER, "Retrieve HEAD version of '" + path + "'"); // retrieve HEAD version File headFile = provider.switchToVersion(path, null); // get version id String headVersion = provider.getVersion(path); // save it to prepared dir r.versionHead = headVersion; r.fileHead = provider.toPrepared(headFile); return r; } public static void rebaseAndCommit(Prepared prep, RemoteRepositoryProvider provider, File projectDir, String path, IRebase rebaser) throws Exception { if (!provider.isUnderMapping(path)) { throw new RuntimeException("Path is not under mapping: " + path); } Log.logDebug(LOGGER, "Rebase and commit '" + path + "'"); final String currentBaseVersion; String savedVersion = provider.getTeamSettings().get(VERSION_PREFIX + path); if (savedVersion != null) { currentBaseVersion = savedVersion; } else { // version wasn't stored - assume latest. TODO Probably need to ask ? provider.switchToVersion(path, null); currentBaseVersion = provider.getVersion(path); } final File localFile = new File(projectDir, path); final boolean fileChangedLocally; { File baseRepoFile = null; if (prep != null && prep.versionBase.equals(currentBaseVersion)) { baseRepoFile = prep.fileBase; } if (baseRepoFile == null) { baseRepoFile = provider.switchToVersion(path, currentBaseVersion); } if (!localFile.exists()) { // there is no local file - just use remote Log.logDebug(LOGGER, "local file '" + path + "' doesn't exist"); fileChangedLocally = false; } else if (FileUtils.contentEquals(baseRepoFile, localFile)) { // versioned file was not changed - no need to commit Log.logDebug(LOGGER, "local file '" + path + "' wasn't changed"); fileChangedLocally = false; } else { Log.logDebug(LOGGER, "local file '" + path + "' was changed"); fileChangedLocally = true; rebaser.parseBaseFile(baseRepoFile); } // baseRepoFile is not valid anymore because we will switch to other version } File headRepoFile = null; String headVersion = null; if (prep != null) { headVersion = prep.versionHead; headRepoFile = prep.fileHead; } if (headVersion == null) { headRepoFile = provider.switchToVersion(path, null); headVersion = provider.getVersion(path); } final boolean fileChangedRemotely; { if (!localFile.exists()) { // there is no local file - just use remote if (headRepoFile.exists()) { fileChangedRemotely = true; rebaser.parseHeadFile(headRepoFile); } else { // there is no remote file also fileChangedRemotely = false; } } else if (StringUtils.equals(currentBaseVersion, headVersion)) { Log.logDebug(LOGGER, "remote file '" + path + "' wasn't changed"); fileChangedRemotely = false; } else { // base and head versions are differ - somebody else committed changes Log.logDebug(LOGGER, "remote file '" + path + "' was changed"); fileChangedRemotely = true; rebaser.parseHeadFile(headRepoFile); } } final File tempOut = new File(projectDir, path + "#based_on_" + headVersion); if (tempOut.exists() && !tempOut.delete()) { throw new Exception("Unable to delete previous temp file"); } boolean needBackup = false; if (fileChangedLocally && fileChangedRemotely) { // rebase need only in case file was changed locally AND remotely Log.logDebug(LOGGER, "rebase and save '" + path + "'"); needBackup = true; rebaser.rebaseAndSave(tempOut); } else if (fileChangedLocally && !fileChangedRemotely) { // only local changes - just use local file Log.logDebug(LOGGER, "only local changes - just use local file '" + path + "'"); } else if (!fileChangedLocally && fileChangedRemotely) { // only remote changes - get remote Log.logDebug(LOGGER, "only remote changes - get remote '" + path + "'"); needBackup = true; if (headRepoFile.exists()) {// otherwise file was removed remotelly FileUtils.copyFile(headRepoFile, tempOut); } } else { Log.logDebug(LOGGER, "there are no changes '" + path + "'"); // there are no changes } if (needBackup) { // new file was saved, need to update version // code below tries to update file "in transaction" with update version if (localFile.exists()) { final File bakTemp = new File(projectDir, path + "#oldbased_on_" + currentBaseVersion); bakTemp.delete(); FileUtils.moveFile(localFile, bakTemp); } provider.getTeamSettings().set(VERSION_PREFIX + path, headVersion); if (tempOut.exists()) { localFile.delete(); FileUtils.moveFile(tempOut, localFile); } } if (prep != null) { prep.needToCommit = fileChangedLocally; prep.commitComment = rebaser.getCommentForCommit(); if (fileChangedLocally) { prep.charset = rebaser.getFileCharset(localFile); } // no need to commit yet - it will make other thread after return; } else if (fileChangedLocally) { // new file already saved - need to commit String comment = rebaser.getCommentForCommit(); provider.copyFilesFromProjectToRepo(path, rebaser.getFileCharset(localFile)); String newVersion = provider.commitFileAfterVersion(path, comment, headVersion, null); if (newVersion != null) { // file was committed good provider.getTeamSettings().set(VERSION_PREFIX + path, newVersion); } } } /** * Commit later. */ public static String commitPrepared(Prepared prep, RemoteRepositoryProvider provider, String possibleHeadVersion) throws Exception { if (!prep.needToCommit) { // there was no changes return null; } provider.copyFilesFromProjectToRepo(prep.path, prep.charset); String newVersion = provider.commitFileAfterVersion(prep.path, prep.commitComment, prep.versionHead, possibleHeadVersion); if (newVersion != null) { // file was committed good provider.getTeamSettings().set(VERSION_PREFIX + prep.path, newVersion); } return newVersion; } public interface IRebase { /** * Rebaser should read and parse BASE version of file. It can't just remember file path because file * will be removed after switch into other version. Rebase can be called after that or can not be * called. * * Case for non-exist file: it's correct call. That means file is just created in local box. But after * that, remote repository can also contain file, i.e. two users created file independently, then * rebase will be called. Implementation should interpret non-exist file as empty data. */ void parseBaseFile(File file) throws Exception; /** * Rebaser should read and parse HEAD version of file. It can't just remember file path because file * will be removed after switch into other version. Rebase can be called after that or can not be * called. * * Case for non-exist file: it's correct call. That means file was removed from repository. * Implementation should interpret non-exist file as empty data. */ void parseHeadFile(File file) throws Exception; /** * Rebase using BASE, HEAD and non-committed version should be processed. At this time parseBaseFile * and parseHeadFile was already called. Keep in mind that this method can display some dialogs to * user, i.e. can work up to some minutes. */ void rebaseAndSave(File out) throws Exception; /** * Construct commit message. */ String getCommentForCommit(); /** * Get charset of file for convert EOL to repository. Implementation can return null if conversion not * required. */ String getFileCharset(File file) throws Exception; } /** * Info about prepared file. */ public static class Prepared { public String path; public File fileBase, fileHead; public String versionBase, versionHead; public boolean needToCommit; public String commitComment; public String charset; } }