/* * This file is part of Fim - File Integrity Manager * * Copyright (C) 2017 Etienne Vrignaud * * Fim 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. * * Fim 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 Fim. If not, see <http://www.gnu.org/licenses/>. */ package org.fim.command; import org.fim.command.exception.BadFimUsageException; import org.fim.command.exception.DontWantToContinueException; import org.fim.internal.SettingsManager; import org.fim.internal.StateComparator; import org.fim.internal.StateGenerator; import org.fim.internal.StateManager; import org.fim.internal.StateReGenerator; import org.fim.model.CompareResult; import org.fim.model.Context; import org.fim.model.Difference; import org.fim.model.FileState; import org.fim.model.HashMode; import org.fim.model.Modification; import org.fim.model.ModificationCounts; import org.fim.model.State; import org.fim.util.Logger; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.stream.Collectors; import static org.fim.internal.StateComparator.resetFileStates; import static org.fim.model.HashMode.dontHash; import static org.fim.model.Modification.attributesModified; import static org.fim.model.Modification.dateModified; import static org.fim.model.Modification.deleted; import static org.fim.util.FileStateUtil.buildFileNamesMap; public class CommitCommand extends AbstractCommand { SettingsManager settingsManager; StateManager manager; @Override public String getCmdName() { return "commit"; } @Override public String getShortCmdName() { return "ci"; } @Override public String getDescription() { return "Commit the current directory State"; } @Override public Object execute(Context context) throws Exception { settingsManager = new SettingsManager(context); HashMode globalHashMode = settingsManager.getGlobalHashMode(); if (context.getHashMode() == dontHash && globalHashMode != dontHash) { Logger.error("Computing hash is mandatory"); throw new BadFimUsageException(); } if (context.getIgnored().somethingIgnored()) { Logger.error("Not allowed to ignore any aspect while committing"); throw new BadFimUsageException(); } adjustThreadCount(context); if (context.getComment().length() == 0) { Logger.out.println("No comment provided. You are going to commit your modifications without any comment."); if (!confirmAction(context, "continue")) { throw new DontWantToContinueException(); } } manager = new StateManager(context); State currentState = new StateGenerator(context).generateState(context.getComment(), context.getRepositoryRootDir(), context.getCurrentDirectory()); State lastState = manager.loadLastState(); State lastStateToCompare = lastState; if (context.isInvokedFromSubDirectory()) { if (!lastState.getModelVersion().equals(currentState.getModelVersion())) { Logger.error("Not able to incrementally commit into the last State that use a different model version."); throw new BadFimUsageException(); } lastStateToCompare = lastState.filterDirectory(context.getRepositoryRootDir(), context.getCurrentDirectory(), true); } CompareResult result = new StateComparator(context, lastStateToCompare, currentState).compare(); if (result.somethingModified()) { commitModifications(context, currentState, lastState, result); } result.displayChanges("Nothing committed"); return result; } private void commitModifications(Context context, State originalCurrentState, State originalLastState, CompareResult result) throws Exception { State currentState = originalCurrentState; State lastState = originalLastState; HashMode initialHashMode = context.getHashMode(); try { currentState.setModificationCounts(result.getModificationCounts()); // Add all the deleted FileStates in order to be saved into the State List<FileState> deletedFileStates = result.getDeleted().stream().map(Difference::getFileState).collect(Collectors.toList()); currentState.getFileStates().addAll(deletedFileStates); HashMode globalHashMode = settingsManager.getGlobalHashMode(); if (initialHashMode != dontHash && initialHashMode != globalHashMode) { // Reload the last state with the globalHashMode in order to get a complete state. context.setHashMode(globalHashMode); currentState.setHashMode(globalHashMode); currentState.getCommitDetails().setHashModeUsedToGetTheStatus(initialHashMode); lastState = manager.loadLastState(); retrieveMissingHash(context, currentState, lastState); } if (context.isInvokedFromSubDirectory()) { currentState = createConsolidatedState(context, lastState, currentState); setFromSubDirectory(context, currentState); } manager.createNewState(currentState); if (context.isPurgeStates()) { PurgeStatesCommand purgeStatesCommand = new PurgeStatesCommand(); purgeStatesCommand.execute(context); } } finally { context.setHashMode(initialHashMode); } } private void setFromSubDirectory(Context context, State currentState) { String currentDir = context.getAbsoluteCurrentDirectory().toString(); String rootDir = context.getRepositoryRootDir().toString() + "/"; String fromSubDirectory = currentDir.replace(rootDir, ""); currentState.getCommitDetails().setFromSubDirectory(fromSubDirectory); } private void retrieveMissingHash(Context context, State currentState, State lastState) throws NoSuchAlgorithmException { Map<String, FileState> lastFileStateMap = buildFileNamesMap(lastState.getFileStates()); List<FileState> toReHash = new ArrayList<>(); for (FileState fileState : currentState.getFileStates()) { Modification modification = fileState.getModification(); if (modification == null || modification == attributesModified || modification == dateModified) { // Get in the last State the hash of the unmodified files FileState lastFileState = lastFileStateMap.get(fileState.getFileName()); if (lastFileState == null) { throw new IllegalStateException(String.format("Not able to find file '%s' into the previous state", fileState.getFileName())); } fileState.setFileHash(lastFileState.getFileHash()); } else if (modification != deleted) { // Hash changed, we need to compute all the mandatory hash toReHash.add(fileState); } } StateReGenerator stateReGenerator = new StateReGenerator(context); stateReGenerator.reHashFiles(toReHash); } private State createConsolidatedState(Context context, State lastState, State currentState) { State filteredState = lastState.filterDirectory(context.getRepositoryRootDir(), context.getCurrentDirectory(), false); resetFileStates(filteredState.getFileStates()); State consolidatedState = currentState.clone(); consolidatedState.getFileStates().addAll(filteredState.getFileStates()); consolidatedState.setModificationCounts(new ModificationCounts(consolidatedState.getFileStates())); consolidatedState.getIgnoredFiles().addAll(lastState.getIgnoredFiles()); return consolidatedState; } }