mirror of
				https://github.com/scm-manager/scm-manager.git
				synced 2025-10-31 10:35:56 +01:00 
			
		
		
		
	| @@ -0,0 +1,166 @@ | ||||
| /* | ||||
|  * MIT License | ||||
|  * | ||||
|  * Copyright (c) 2020-present Cloudogu GmbH and Contributors | ||||
|  * | ||||
|  * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
|  * of this software and associated documentation files (the "Software"), to deal | ||||
|  * in the Software without restriction, including without limitation the rights | ||||
|  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
|  * copies of the Software, and to permit persons to whom the Software is | ||||
|  * furnished to do so, subject to the following conditions: | ||||
|  * | ||||
|  * The above copyright notice and this permission notice shall be included in all | ||||
|  * copies or substantial portions of the Software. | ||||
|  * | ||||
|  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
|  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
|  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
|  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
|  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
|  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
|  * SOFTWARE. | ||||
|  */ | ||||
|  | ||||
| package sonia.scm.repository.spi; | ||||
|  | ||||
| import org.apache.shiro.SecurityUtils; | ||||
| import org.tmatesoft.svn.core.SVNException; | ||||
| import org.tmatesoft.svn.core.SVNLock; | ||||
| import org.tmatesoft.svn.core.auth.ISVNAuthenticationManager; | ||||
| import org.tmatesoft.svn.core.io.SVNRepository; | ||||
| import sonia.scm.repository.InternalRepositoryException; | ||||
| import sonia.scm.repository.api.FileLock; | ||||
| import sonia.scm.repository.api.FileLockedException; | ||||
| import sonia.scm.repository.api.LockCommandResult; | ||||
| import sonia.scm.repository.api.UnlockCommandResult; | ||||
|  | ||||
| import java.time.Instant; | ||||
| import java.util.Arrays; | ||||
| import java.util.Collection; | ||||
| import java.util.Optional; | ||||
| import java.util.function.Predicate; | ||||
|  | ||||
| import static java.util.Collections.singletonMap; | ||||
| import static java.util.Optional.ofNullable; | ||||
| import static java.util.stream.Collectors.toList; | ||||
| import static org.tmatesoft.svn.core.auth.BasicAuthenticationManager.newInstance; | ||||
| import static sonia.scm.ContextEntry.ContextBuilder.entity; | ||||
|  | ||||
| public class SvnFileLockCommand extends AbstractSvnCommand implements FileLockCommand { | ||||
|  | ||||
|   private static final String LOCK_MESSAGE_PREFIX = "locked by SCM-Manager for "; | ||||
|  | ||||
|   protected SvnFileLockCommand(SvnContext context) { | ||||
|     super(context); | ||||
|   } | ||||
|  | ||||
|   @Override | ||||
|   public LockCommandResult lock(LockCommandRequest request) { | ||||
|     String fileToLock = request.getFile(); | ||||
|     try { | ||||
|       doLock(fileToLock); | ||||
|       return new LockCommandResult(true); | ||||
|     } catch (SVNException e) { | ||||
|       throw new InternalRepositoryException(entity("File", fileToLock).in(repository), "failed to lock file", e); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   private void doLock(String fileToLock) throws SVNException { | ||||
|     SVNRepository svnRepository = open(); | ||||
|     String currentUser = initializeAuthentication(svnRepository); | ||||
|     getFileLock(fileToLock, svnRepository) | ||||
|       .ifPresent(lock -> { | ||||
|         throw new FileLockedException(repository.getNamespaceAndName(), lock); | ||||
|       }); | ||||
|     svnRepository.lock(singletonMap(fileToLock, null), LOCK_MESSAGE_PREFIX + currentUser, false, null); | ||||
|   } | ||||
|  | ||||
|   @Override | ||||
|   public UnlockCommandResult unlock(UnlockCommandRequest request) { | ||||
|     return unlock(request, any -> true); | ||||
|   } | ||||
|  | ||||
|   public UnlockCommandResult unlock(UnlockCommandRequest request, Predicate<SvnFileLock> predicate) { | ||||
|     String fileToUnlock = request.getFile(); | ||||
|     try { | ||||
|       doUnlock(request, predicate); | ||||
|       return new UnlockCommandResult(true); | ||||
|     } catch (SVNException e) { | ||||
|       throw new InternalRepositoryException(entity("File", fileToUnlock).in(repository), "failed to unlock file", e); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   private void doUnlock(UnlockCommandRequest request, Predicate<SvnFileLock> predicate) throws SVNException { | ||||
|     String fileToUnlock = request.getFile(); | ||||
|     SVNRepository svnRepository = open(); | ||||
|     initializeAuthentication(svnRepository); | ||||
|     Optional<SvnFileLock> fileLock = getFileLock(fileToUnlock, svnRepository); | ||||
|     if (fileLock.isPresent()) { | ||||
|       SvnFileLock lock = fileLock.get(); | ||||
|       if (shouldPreventUnlock(request, predicate, lock)) { | ||||
|         throw new FileLockedException(repository.getNamespaceAndName(), lock); | ||||
|       } | ||||
|       svnRepository.unlock(singletonMap(fileToUnlock, lock.getId()), request.isForce(), null); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   @Override | ||||
|   public Optional<FileLock> status(LockStatusCommandRequest request) { | ||||
|     String file = request.getFile(); | ||||
|     try { | ||||
|       return getFileLock(file, open()).map(lock -> lock); | ||||
|     } catch (SVNException e) { | ||||
|       throw new InternalRepositoryException(entity("File", file).in(repository), "failed to read lock status", e); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   @Override | ||||
|   public Collection<FileLock> getAll() { | ||||
|     try { | ||||
|       SVNRepository svnRepository = open(); | ||||
|       return Arrays.stream(svnRepository.getLocks("/")) | ||||
|         .map(this::createLock) | ||||
|         .collect(toList()); | ||||
|     } catch (SVNException e) { | ||||
|       throw new InternalRepositoryException(repository, "failed to read locks", e); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   private Optional<SvnFileLock> getFileLock(String file, SVNRepository svnRepository) throws SVNException { | ||||
|     return ofNullable(svnRepository.getLock(file)).map(this::createLock); | ||||
|   } | ||||
|  | ||||
|   private SvnFileLock createLock(SVNLock lock) { | ||||
|     String path = lock.getPath(); | ||||
|     if (path.startsWith("/")) { | ||||
|       path = path.substring(1); | ||||
|     } | ||||
|     return new SvnFileLock(path, lock.getID(), lock.getOwner(), lock.getCreationDate().toInstant(), lock.getComment()); | ||||
|   } | ||||
|  | ||||
|   private boolean shouldPreventUnlock(UnlockCommandRequest request, Predicate<SvnFileLock> predicate, SvnFileLock lock) { | ||||
|     return !request.isForce() && !getCurrentUser().equals(lock.getUserId()) || !predicate.test(lock); | ||||
|   } | ||||
|  | ||||
|   private String initializeAuthentication(SVNRepository svnRepository) { | ||||
|     String currentUser = getCurrentUser(); | ||||
|     ISVNAuthenticationManager authenticationManager = newInstance(currentUser, null); | ||||
|     svnRepository.setAuthenticationManager(authenticationManager); | ||||
|     return currentUser; | ||||
|   } | ||||
|  | ||||
|   private String getCurrentUser() { | ||||
|     return SecurityUtils.getSubject().getPrincipal().toString(); | ||||
|   } | ||||
|  | ||||
|   static class SvnFileLock extends FileLock { | ||||
|     private SvnFileLock(String path, String id, String userId, Instant timestamp, String message) { | ||||
|       super(path, id, userId, timestamp, message); | ||||
|     } | ||||
|  | ||||
|     boolean isCreatedByScmManager() { | ||||
|       return getMessage().filter(message -> message.startsWith(LOCK_MESSAGE_PREFIX)).isPresent(); | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @@ -34,7 +34,6 @@ import org.tmatesoft.svn.core.wc.SVNClientManager; | ||||
| import org.tmatesoft.svn.core.wc.SVNWCClient; | ||||
| import org.tmatesoft.svn.core.wc.SVNWCUtil; | ||||
| import sonia.scm.ConcurrentModificationException; | ||||
| import sonia.scm.ContextEntry; | ||||
| import sonia.scm.repository.InternalRepositoryException; | ||||
| import sonia.scm.repository.Repository; | ||||
| import sonia.scm.repository.SvnWorkingCopyFactory; | ||||
| @@ -57,10 +56,13 @@ public class SvnModifyCommand implements ModifyCommand { | ||||
|   private final SvnWorkingCopyFactory workingCopyFactory; | ||||
|   private final Repository repository; | ||||
|  | ||||
|   private final SvnFileLockCommand lockCommand; | ||||
|  | ||||
|   SvnModifyCommand(SvnContext context, SvnWorkingCopyFactory workingCopyFactory) { | ||||
|     this.context = context; | ||||
|     this.repository = context.getRepository(); | ||||
|     this.workingCopyFactory = workingCopyFactory; | ||||
|     this.lockCommand = new SvnFileLockCommand(context); | ||||
|   } | ||||
|  | ||||
|   @Override | ||||
| @@ -137,6 +139,7 @@ public class SvnModifyCommand implements ModifyCommand { | ||||
|  | ||||
|     @Override | ||||
|     public void doScmDelete(String toBeDeleted) { | ||||
|       unlock(toBeDeleted); | ||||
|       try { | ||||
|         wcClient.doDelete(new File(workingDirectory, toBeDeleted), true, true, false); | ||||
|       } catch (SVNException e) { | ||||
| @@ -146,6 +149,7 @@ public class SvnModifyCommand implements ModifyCommand { | ||||
|  | ||||
|     @Override | ||||
|     public void addFileToScm(String name, Path file) { | ||||
|       unlock(name); | ||||
|       try { | ||||
|         wcClient.doAdd( | ||||
|           file.toFile(), | ||||
| @@ -161,6 +165,19 @@ public class SvnModifyCommand implements ModifyCommand { | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     private void unlock(String toBeDeleted) { | ||||
|       lockCommand.unlock( | ||||
|         createUnlockRequest(toBeDeleted), | ||||
|         SvnFileLockCommand.SvnFileLock::isCreatedByScmManager | ||||
|       ); | ||||
|     } | ||||
|  | ||||
|     private UnlockCommandRequest createUnlockRequest(String toBeDeleted) { | ||||
|       UnlockCommandRequest request = new UnlockCommandRequest(); | ||||
|       request.setFile(toBeDeleted); | ||||
|       return request; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public File getWorkDir() { | ||||
|       return workingDirectory; | ||||
|   | ||||
| @@ -55,7 +55,8 @@ public class SvnRepositoryServiceProvider extends RepositoryServiceProvider { | ||||
|     Command.MODIFY, | ||||
|     Command.LOOKUP, | ||||
|     Command.FULL_HEALTH_CHECK, | ||||
|     Command.MIRROR | ||||
|     Command.MIRROR, | ||||
|     Command.FILE_LOCK | ||||
|   ); | ||||
|  | ||||
|   public static final Set<Feature> FEATURES = EnumSet.of( | ||||
| @@ -155,4 +156,9 @@ public class SvnRepositoryServiceProvider extends RepositoryServiceProvider { | ||||
|   public MirrorCommand getMirrorCommand() { | ||||
|     return new SvnMirrorCommand(context, trustManager, globalProxyConfiguration); | ||||
|   } | ||||
|  | ||||
|   @Override | ||||
|   public FileLockCommand getFileLockCommand() { | ||||
|     return new SvnFileLockCommand(context); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -0,0 +1,259 @@ | ||||
| /* | ||||
|  * MIT License | ||||
|  * | ||||
|  * Copyright (c) 2020-present Cloudogu GmbH and Contributors | ||||
|  * | ||||
|  * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
|  * of this software and associated documentation files (the "Software"), to deal | ||||
|  * in the Software without restriction, including without limitation the rights | ||||
|  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
|  * copies of the Software, and to permit persons to whom the Software is | ||||
|  * furnished to do so, subject to the following conditions: | ||||
|  * | ||||
|  * The above copyright notice and this permission notice shall be included in all | ||||
|  * copies or substantial portions of the Software. | ||||
|  * | ||||
|  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
|  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
|  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
|  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
|  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
|  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
|  * SOFTWARE. | ||||
|  */ | ||||
|  | ||||
| package sonia.scm.repository.spi; | ||||
|  | ||||
| import org.apache.shiro.subject.Subject; | ||||
| import org.apache.shiro.util.ThreadContext; | ||||
| import org.junit.After; | ||||
| import org.junit.Before; | ||||
| import org.junit.Test; | ||||
| import org.tmatesoft.svn.core.SVNException; | ||||
| import org.tmatesoft.svn.core.SVNLock; | ||||
| import org.tmatesoft.svn.core.io.SVNRepository; | ||||
| import sonia.scm.repository.api.FileLock; | ||||
| import sonia.scm.repository.api.FileLockedException; | ||||
| import sonia.scm.repository.api.LockCommandResult; | ||||
| import sonia.scm.repository.api.UnlockCommandResult; | ||||
|  | ||||
| import java.util.Collection; | ||||
| import java.util.Optional; | ||||
|  | ||||
| import static org.assertj.core.api.Assertions.assertThat; | ||||
| import static org.junit.Assert.assertThrows; | ||||
| import static org.mockito.Mockito.mock; | ||||
| import static org.mockito.Mockito.when; | ||||
|  | ||||
| public class SvnFileLockCommandTest extends AbstractSvnCommandTestBase { | ||||
|  | ||||
|   @Before | ||||
|   public void mockDefaultSubject() { | ||||
|     mockSubject("trillian"); | ||||
|   } | ||||
|  | ||||
|   private void mockSubject(String name) { | ||||
|     Subject subject = mock(Subject.class); | ||||
|     ThreadContext.bind(subject); | ||||
|     when(subject.getPrincipal()).thenReturn(name); | ||||
|   } | ||||
|  | ||||
|   @After | ||||
|   public void unbindSubject() { | ||||
|     ThreadContext.unbindSubject(); | ||||
|   } | ||||
|  | ||||
|   @Test | ||||
|   public void shouldLockFile() throws SVNException { | ||||
|     SvnFileLockCommand lockCommand = new SvnFileLockCommand(createContext()); | ||||
|  | ||||
|     LockCommandRequest request = new LockCommandRequest(); | ||||
|     request.setFile("a.txt"); | ||||
|  | ||||
|     LockCommandResult lockResult = lockCommand.lock(request); | ||||
|  | ||||
|     assertThat(lockResult.isSuccessful()).isTrue(); | ||||
|     assertSingleLock("a.txt", "trillian"); | ||||
|   } | ||||
|  | ||||
|   @Test | ||||
|   public void shouldUnlockFile() throws SVNException { | ||||
|     createLock("a.txt"); | ||||
|  | ||||
|     SvnFileLockCommand lockCommand = new SvnFileLockCommand(createContext()); | ||||
|     UnlockCommandRequest request = new UnlockCommandRequest(); | ||||
|     request.setFile("a.txt"); | ||||
|  | ||||
|     UnlockCommandResult unlockResult = lockCommand.unlock(request); | ||||
|  | ||||
|     assertThat(unlockResult.isSuccessful()).isTrue(); | ||||
|     assertThat(getLocks("a.txt")).isEmpty(); | ||||
|   } | ||||
|  | ||||
|   @Test | ||||
|   public void shouldUnlockFileOnlyIfPredicateMatches() throws SVNException { | ||||
|     createLock("a.txt"); | ||||
|  | ||||
|     SvnFileLockCommand lockCommand = new SvnFileLockCommand(createContext()); | ||||
|     UnlockCommandRequest request = new UnlockCommandRequest(); | ||||
|     request.setFile("a.txt"); | ||||
|  | ||||
|     assertThrows(FileLockedException.class, () -> lockCommand.unlock(request, lock -> false)); | ||||
|     assertThat(getLocks("a.txt")).isNotEmpty(); | ||||
|  | ||||
|     lockCommand.unlock(request, lock -> true); | ||||
|     assertThat(getLocks("a.txt")).isEmpty(); | ||||
|   } | ||||
|  | ||||
|   @Test | ||||
|   public void shouldNotFailUnlockingNotLockedFile() throws SVNException { | ||||
|     SvnFileLockCommand lockCommand = new SvnFileLockCommand(createContext()); | ||||
|     UnlockCommandRequest request = new UnlockCommandRequest(); | ||||
|     request.setFile("a.txt"); | ||||
|  | ||||
|     UnlockCommandResult unlockResult = lockCommand.unlock(request); | ||||
|  | ||||
|     assertThat(unlockResult.isSuccessful()).isTrue(); | ||||
|     assertThat(getLocks("a.txt")).isEmpty(); | ||||
|   } | ||||
|  | ||||
|   @Test | ||||
|   public void shouldFailToUnlockFileLockedByOtherUser() throws SVNException { | ||||
|     createLockFromOtherUser(); | ||||
|  | ||||
|     SvnFileLockCommand lockCommand = new SvnFileLockCommand(createContext()); | ||||
|     UnlockCommandRequest request = new UnlockCommandRequest(); | ||||
|     request.setFile("a.txt"); | ||||
|  | ||||
|     assertThrows( | ||||
|       "File a.txt locked by dent.", | ||||
|       FileLockedException.class, | ||||
|       () -> lockCommand.unlock(request)); | ||||
|     assertThat(getLocks("a.txt")).isNotEmpty(); | ||||
|   } | ||||
|  | ||||
|   @Test | ||||
|   public void shouldUnlockFileLockedByOtherUserWithForce() throws SVNException { | ||||
|     createLockFromOtherUser(); | ||||
|  | ||||
|     SvnFileLockCommand lockCommand = new SvnFileLockCommand(createContext()); | ||||
|     UnlockCommandRequest request = new UnlockCommandRequest(); | ||||
|     request.setFile("a.txt"); | ||||
|     request.setForce(true); | ||||
|  | ||||
|     UnlockCommandResult unlockResult = lockCommand.unlock(request); | ||||
|  | ||||
|     assertThat(unlockResult.isSuccessful()).isTrue(); | ||||
|     assertThat(getLocks("a.txt")).isEmpty(); | ||||
|   } | ||||
|  | ||||
|   @Test | ||||
|   public void shouldNotOverwriteLockFromOtherUser() { | ||||
|     createLockFromOtherUser(); | ||||
|  | ||||
|     SvnFileLockCommand lockCommand = new SvnFileLockCommand(createContext()); | ||||
|  | ||||
|     LockCommandRequest request = new LockCommandRequest(); | ||||
|     request.setFile("a.txt"); | ||||
|  | ||||
|     assertThrows( | ||||
|       "File a.txt locked by dent.", | ||||
|       FileLockedException.class, | ||||
|       () -> lockCommand.lock(request)); | ||||
|   } | ||||
|  | ||||
|   @Test | ||||
|   public void shouldNotRemoveLockFromOtherUserEvenWithPredicate() { | ||||
|     createLockFromOtherUser(); | ||||
|  | ||||
|     SvnFileLockCommand lockCommand = new SvnFileLockCommand(createContext()); | ||||
|  | ||||
|     UnlockCommandRequest request = new UnlockCommandRequest(); | ||||
|     request.setFile("a.txt"); | ||||
|  | ||||
|     assertThrows( | ||||
|       "File a.txt locked by dent.", | ||||
|       FileLockedException.class, | ||||
|       () -> lockCommand.unlock(request, lock -> true)); | ||||
|   } | ||||
|  | ||||
|   @Test | ||||
|   public void shouldGetEmptyLocksWithoutLocks() { | ||||
|     SvnFileLockCommand lockCommand = new SvnFileLockCommand(createContext()); | ||||
|  | ||||
|     Collection<FileLock> locks = lockCommand.getAll(); | ||||
|  | ||||
|     assertThat(locks).isEmpty(); | ||||
|   } | ||||
|  | ||||
|   @Test | ||||
|   public void shouldGetLocks() { | ||||
|     createLock("a.txt"); | ||||
|     createLock("c/e.txt"); | ||||
|  | ||||
|     SvnFileLockCommand lockCommand = new SvnFileLockCommand(createContext()); | ||||
|  | ||||
|     Collection<FileLock> locks = lockCommand.getAll(); | ||||
|  | ||||
|     assertThat(locks) | ||||
|       .hasSize(2) | ||||
|       .extracting("path") | ||||
|       .containsExactly("a.txt", "c/e.txt"); | ||||
|   } | ||||
|  | ||||
|   @Test | ||||
|   public void shouldGetNoStatusForUnlockedFile() { | ||||
|     SvnFileLockCommand lockCommand = new SvnFileLockCommand(createContext()); | ||||
|  | ||||
|     LockStatusCommandRequest lockStatusCommandRequest = new LockStatusCommandRequest(); | ||||
|     lockStatusCommandRequest.setFile("a.txt"); | ||||
|     Optional<FileLock> status = lockCommand.status(lockStatusCommandRequest); | ||||
|  | ||||
|     assertThat(status).isEmpty(); | ||||
|   } | ||||
|  | ||||
|   @Test | ||||
|   public void shouldGetStatusForLockedFile() { | ||||
|     createLock("a.txt"); | ||||
|  | ||||
|     SvnFileLockCommand lockCommand = new SvnFileLockCommand(createContext()); | ||||
|  | ||||
|     LockStatusCommandRequest lockStatusCommandRequest = new LockStatusCommandRequest(); | ||||
|     lockStatusCommandRequest.setFile("a.txt"); | ||||
|     Optional<FileLock> status = lockCommand.status(lockStatusCommandRequest); | ||||
|  | ||||
|     assertThat(status) | ||||
|       .get() | ||||
|       .extracting("userId") | ||||
|       .isEqualTo("trillian"); | ||||
|   } | ||||
|  | ||||
|   private void createLockFromOtherUser() { | ||||
|     mockSubject("dent"); | ||||
|     createLock("a.txt"); | ||||
|     mockDefaultSubject(); | ||||
|   } | ||||
|  | ||||
|   private void createLock(String path) { | ||||
|     SvnFileLockCommand lockCommand = new SvnFileLockCommand(createContext()); | ||||
|     LockCommandRequest request = new LockCommandRequest(); | ||||
|     request.setFile(path); | ||||
|     lockCommand.lock(request); | ||||
|   } | ||||
|  | ||||
|   private void assertSingleLock(String path, String user) throws SVNException { | ||||
|     SVNLock[] locks = getLocks(path); | ||||
|     assertThat(locks) | ||||
|       .extracting("path") | ||||
|       .containsExactly("/" + | ||||
|         path); | ||||
|     assertThat(locks) | ||||
|       .extracting("owner") | ||||
|       .containsExactly(user); | ||||
|   } | ||||
|  | ||||
|   private SVNLock[] getLocks(String path) throws SVNException { | ||||
|     SVNRepository svnRepository = createContext().open(); | ||||
|     return svnRepository.getLocks(path); | ||||
|   } | ||||
| } | ||||
| @@ -35,12 +35,15 @@ import org.junit.rules.TemporaryFolder; | ||||
| import sonia.scm.AlreadyExistsException; | ||||
| import sonia.scm.ConcurrentModificationException; | ||||
| import sonia.scm.repository.Person; | ||||
| import sonia.scm.repository.api.FileLock; | ||||
| import sonia.scm.repository.api.FileLockedException; | ||||
| import sonia.scm.repository.work.NoneCachingWorkingCopyPool; | ||||
| import sonia.scm.repository.work.WorkdirProvider; | ||||
| import sonia.scm.repository.work.WorkingCopy; | ||||
|  | ||||
| import java.io.File; | ||||
| import java.io.IOException; | ||||
| import java.util.Optional; | ||||
|  | ||||
| import static org.assertj.core.api.Assertions.assertThat; | ||||
| import static org.junit.jupiter.api.Assertions.assertThrows; | ||||
| @@ -191,4 +194,54 @@ public class SvnModifyCommandTest extends AbstractSvnCommandTestBase { | ||||
|  | ||||
|     // nothing to check here; we just want to ensure that no exception is thrown | ||||
|   } | ||||
|  | ||||
|   @Test | ||||
|   public void shouldFailIfLockedByOtherPerson() { | ||||
|     Subject subject = mock(Subject.class); | ||||
|     when(subject.getPrincipal()).thenReturn("Perrin"); | ||||
|     ThreadContext.bind(subject); | ||||
|  | ||||
|     lockFile(); | ||||
|  | ||||
|     initSecurityManager(); | ||||
|  | ||||
|     ModifyCommandRequest request = new ModifyCommandRequest(); | ||||
|     request.addRequest(new ModifyCommandRequest.DeleteFileRequest("a.txt", false)); | ||||
|     request.setCommitMessage("this should not happen"); | ||||
|     request.setAuthor(new Person("Arthur Dent", "dent@hitchhiker.com")); | ||||
|  | ||||
|     assertThrows(FileLockedException.class, () -> svnModifyCommand.execute(request)); | ||||
|     WorkingCopy<File, File> workingCopy = workingCopyFactory.createWorkingCopy(context, null); | ||||
|     assertThat(new File(workingCopy.getWorkingRepository().getAbsolutePath() + "/a.txt")).exists(); | ||||
|   } | ||||
|  | ||||
|   @Test | ||||
|   public void shouldSucceedIfLockedByUser() { | ||||
|     lockFile(); | ||||
|  | ||||
|     ModifyCommandRequest request = new ModifyCommandRequest(); | ||||
|     request.addRequest(new ModifyCommandRequest.DeleteFileRequest("a.txt", false)); | ||||
|     request.setCommitMessage("this should not happen"); | ||||
|     request.setAuthor(new Person("Arthur Dent", "dent@hitchhiker.com")); | ||||
|  | ||||
|     svnModifyCommand.execute(request); | ||||
|  | ||||
|     WorkingCopy<File, File> workingCopy = workingCopyFactory.createWorkingCopy(context, null); | ||||
|     assertThat(new File(workingCopy.getWorkingRepository().getAbsolutePath() + "/a.txt")).doesNotExist(); | ||||
|     assertThat(getLock()).isEmpty(); | ||||
|   } | ||||
|  | ||||
|   private void lockFile() { | ||||
|     SvnFileLockCommand svnFileLockCommand = new SvnFileLockCommand(context); | ||||
|     LockCommandRequest lockRequest = new LockCommandRequest(); | ||||
|     lockRequest.setFile("a.txt"); | ||||
|     svnFileLockCommand.lock(lockRequest); | ||||
|   } | ||||
|  | ||||
|   private Optional<FileLock> getLock() { | ||||
|     SvnFileLockCommand svnFileLockCommand = new SvnFileLockCommand(context); | ||||
|     LockStatusCommandRequest request = new LockStatusCommandRequest(); | ||||
|     request.setFile("a.txt"); | ||||
|     return svnFileLockCommand.status(request); | ||||
|   } | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user