From 9c75ff95c8f50fd129c494aebc2e7240d7bcdbfc Mon Sep 17 00:00:00 2001 From: Rene Pfeuffer Date: Thu, 27 Feb 2020 11:42:37 +0100 Subject: [PATCH] Keep file attributes on modification --- CHANGELOG.md | 1 + .../repository/spi/ModifyWorkerHelper.java | 33 +++++++ .../spi/ModifyWorkerHelperTest.java | 90 +++++++++++++++++++ 3 files changed, 124 insertions(+) create mode 100644 scm-core/src/test/java/sonia/scm/repository/spi/ModifyWorkerHelperTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b863c3b5d..2387635e02 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Modification for mercurial repositories with enabled XSRF protection - Does not throw NullPointerException when merge fails without normal merge conflicts +- Keep file attributes on modification ### Removed - Enunciate rest documentation diff --git a/scm-core/src/main/java/sonia/scm/repository/spi/ModifyWorkerHelper.java b/scm-core/src/main/java/sonia/scm/repository/spi/ModifyWorkerHelper.java index a1ff6b0eb3..875e06334a 100644 --- a/scm-core/src/main/java/sonia/scm/repository/spi/ModifyWorkerHelper.java +++ b/scm-core/src/main/java/sonia/scm/repository/spi/ModifyWorkerHelper.java @@ -1,6 +1,8 @@ package sonia.scm.repository.spi; import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import sonia.scm.ContextEntry; import sonia.scm.repository.Repository; @@ -10,8 +12,13 @@ import java.nio.file.FileAlreadyExistsException; import java.nio.file.Files; import java.nio.file.NoSuchFileException; import java.nio.file.Path; +import java.nio.file.attribute.PosixFilePermission; +import java.util.Optional; +import java.util.Set; import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; +import static java.util.Optional.empty; +import static java.util.Optional.of; import static sonia.scm.AlreadyExistsException.alreadyExists; import static sonia.scm.ContextEntry.ContextBuilder.entity; import static sonia.scm.NotFoundException.notFound; @@ -22,6 +29,8 @@ import static sonia.scm.NotFoundException.notFound; */ public interface ModifyWorkerHelper extends ModifyCommand.Worker { + Logger LOG = LoggerFactory.getLogger(ModifyWorkerHelper.class); + @Override default void delete(String toBeDeleted) throws IOException { Path fileToBeDeleted = new File(getWorkDir(), toBeDeleted).toPath(); @@ -57,10 +66,34 @@ public interface ModifyWorkerHelper extends ModifyCommand.Worker { if (!targetFile.toFile().exists()) { throw notFound(createFileContext(path)); } + Optional> posixFilePermissions = getPosixFilePermissions(targetFile); Files.move(file.toPath(), targetFile, REPLACE_EXISTING); + posixFilePermissions.ifPresent(permissions -> setPosixFilePermissions(targetFile, permissions)); addFileToScm(path, targetFile); } + default Optional> getPosixFilePermissions(Path targetFile) { + try { + return of(Files.getPosixFilePermissions(targetFile)); + } catch (UnsupportedOperationException e) { + LOG.trace("posix file permissions not supported"); + return empty(); + } catch (IOException e) { + LOG.info("could not read posix file permissions for file {}", targetFile); + return empty(); + } + } + + default void setPosixFilePermissions(Path targetFile, Set permissions) { + try { + Files.setPosixFilePermissions(targetFile, permissions); + } catch (UnsupportedOperationException e) { + LOG.trace("posix file permissions not supported"); + } catch (IOException e) { + LOG.info("could not write posix file permissions to file {}", targetFile); + } + } + void addFileToScm(String name, Path file); default ContextEntry.ContextBuilder createFileContext(String path) { diff --git a/scm-core/src/test/java/sonia/scm/repository/spi/ModifyWorkerHelperTest.java b/scm-core/src/test/java/sonia/scm/repository/spi/ModifyWorkerHelperTest.java new file mode 100644 index 0000000000..c2996429a1 --- /dev/null +++ b/scm-core/src/test/java/sonia/scm/repository/spi/ModifyWorkerHelperTest.java @@ -0,0 +1,90 @@ +package sonia.scm.repository.spi; + +import com.google.common.collect.ImmutableSet; +import org.assertj.core.api.Assumptions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junitpioneer.jupiter.TempDirectory; +import sonia.scm.repository.Repository; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +import static java.nio.file.attribute.PosixFilePermission.GROUP_EXECUTE; +import static java.nio.file.attribute.PosixFilePermission.GROUP_READ; +import static java.nio.file.attribute.PosixFilePermission.OTHERS_EXECUTE; +import static java.nio.file.attribute.PosixFilePermission.OTHERS_READ; +import static java.nio.file.attribute.PosixFilePermission.OWNER_EXECUTE; +import static java.nio.file.attribute.PosixFilePermission.OWNER_READ; +import static java.nio.file.attribute.PosixFilePermission.OWNER_WRITE; +import static org.assertj.core.api.Assertions.assertThat; + +@ExtendWith(TempDirectory.class) +class ModifyWorkerHelperTest { + + @Test + void shouldKeepExecutableFlag(@TempDirectory.TempDir Path temp) throws IOException { + + File target = createFile(temp, "executable.sh"); + File newFile = createFile(temp, "other"); + + Assumptions.assumeThatThrownBy(() -> { + Files.setPosixFilePermissions(target.toPath(), + ImmutableSet.of(OWNER_READ, OWNER_WRITE, OWNER_EXECUTE, GROUP_READ, GROUP_EXECUTE, OTHERS_READ, OTHERS_EXECUTE)); + Files.setPosixFilePermissions(newFile.toPath(), + ImmutableSet.of(OWNER_READ, OWNER_WRITE, GROUP_READ, OTHERS_READ)); + }).doesNotThrowAnyException(); // ignore this test on systems that does not support posix file permissions + + ModifyWorkerHelper helper = new MinimalModifyWorkerHelper(temp); + + helper.modify("executable.sh", newFile); + + assertThat(Files.getPosixFilePermissions(target.toPath())) + .contains(OWNER_READ, OWNER_WRITE, OWNER_EXECUTE, GROUP_READ, GROUP_EXECUTE, OTHERS_READ, OTHERS_EXECUTE); + } + + private File createFile(Path temp, String fileName) throws IOException { + File file = new File(temp.toFile(), fileName); + FileWriter source = new FileWriter(file); + source.write("something"); + source.close(); + return file; + } + + private static class MinimalModifyWorkerHelper implements ModifyWorkerHelper { + + private final Path temp; + + public MinimalModifyWorkerHelper(Path temp) { + this.temp = temp; + } + + @Override + public void doScmDelete(String toBeDeleted) { + + } + + @Override + public void addFileToScm(String name, Path file) { + + } + + @Override + public File getWorkDir() { + return temp.toFile(); + } + + @Override + public Repository getRepository() { + return null; + } + + @Override + public String getBranch() { + return null; + } + } +}