mirror of https://github.com/halo-dev/halo
Complete theme updating
parent
bf6b11eb2e
commit
0c8955dfcf
|
@ -117,6 +117,13 @@ public class ThemeController {
|
||||||
themeSettingService.save(settings, themeId);
|
themeSettingService.save(settings, themeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PutMapping("{themeId}")
|
||||||
|
public ThemeProperty updateTheme(@PathVariable("themeId") String themeId,
|
||||||
|
@RequestPart(name = "file", required = false) MultipartFile file) {
|
||||||
|
|
||||||
|
return themeService.update(themeId);
|
||||||
|
}
|
||||||
|
|
||||||
@DeleteMapping("{themeId}")
|
@DeleteMapping("{themeId}")
|
||||||
@ApiOperation("Deletes a theme")
|
@ApiOperation("Deletes a theme")
|
||||||
public void deleteBy(@PathVariable("themeId") String themeId) {
|
public void deleteBy(@PathVariable("themeId") String themeId) {
|
||||||
|
@ -145,4 +152,5 @@ public class ThemeController {
|
||||||
public BaseResponse exists(@RequestParam(value = "template") String template) {
|
public BaseResponse exists(@RequestParam(value = "template") String template) {
|
||||||
return BaseResponse.ok(themeService.templateExists(template));
|
return BaseResponse.ok(themeService.templateExists(template));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
package run.halo.app.exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Theme update exception.
|
||||||
|
*
|
||||||
|
* @author johnniang
|
||||||
|
* @date 19-5-30
|
||||||
|
*/
|
||||||
|
public class ThemeUpdateException extends ServiceException {
|
||||||
|
|
||||||
|
public ThemeUpdateException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ThemeUpdateException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,8 @@
|
||||||
package run.halo.app.handler.theme.config.impl;
|
package run.halo.app.handler.theme.config.impl;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
|
||||||
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
|
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
@ -18,7 +20,12 @@ import java.io.IOException;
|
||||||
@Service
|
@Service
|
||||||
public class YamlThemePropertyResolver implements ThemePropertyResolver {
|
public class YamlThemePropertyResolver implements ThemePropertyResolver {
|
||||||
|
|
||||||
private final ObjectMapper yamlMapper = new ObjectMapper(new YAMLFactory());
|
private final ObjectMapper yamlMapper;
|
||||||
|
|
||||||
|
public YamlThemePropertyResolver() {
|
||||||
|
yamlMapper = new ObjectMapper(new YAMLFactory());
|
||||||
|
yamlMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ThemeProperty resolve(String content) throws IOException {
|
public ThemeProperty resolve(String content) throws IOException {
|
||||||
|
|
|
@ -28,6 +28,14 @@ public class ThemeProperty {
|
||||||
*/
|
*/
|
||||||
private String website;
|
private String website;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Theme remote branch.(default is master)
|
||||||
|
*/
|
||||||
|
private String branch;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Theme repo url.
|
||||||
|
*/
|
||||||
private String repo;
|
private String repo;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -51,7 +59,7 @@ public class ThemeProperty {
|
||||||
private Author author;
|
private Author author;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Theme path.
|
* Theme full path.
|
||||||
*/
|
*/
|
||||||
private String themePath;
|
private String themePath;
|
||||||
|
|
||||||
|
|
|
@ -71,6 +71,16 @@ public interface ThemeService {
|
||||||
*/
|
*/
|
||||||
String CUSTOM_SHEET_PREFIX = "sheet_";
|
String CUSTOM_SHEET_PREFIX = "sheet_";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Theme provider remote name.
|
||||||
|
*/
|
||||||
|
String THEME_PROVIDER_REMOTE_NAME = "theme-provider";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default remote branch name.
|
||||||
|
*/
|
||||||
|
String DEFAULT_REMOTE_BRANCH = "master";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get theme property by theme id.
|
* Get theme property by theme id.
|
||||||
*
|
*
|
||||||
|
@ -241,4 +251,13 @@ public interface ThemeService {
|
||||||
* Reloads themes
|
* Reloads themes
|
||||||
*/
|
*/
|
||||||
void reload();
|
void reload();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates theme by theme id.
|
||||||
|
*
|
||||||
|
* @param themeId theme id must not be blank
|
||||||
|
* @return theme property
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
ThemeProperty update(@NonNull String themeId);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,14 @@ package run.halo.app.service.impl;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.eclipse.jgit.api.Git;
|
import org.eclipse.jgit.api.Git;
|
||||||
|
import org.eclipse.jgit.api.MergeCommand;
|
||||||
|
import org.eclipse.jgit.api.PullResult;
|
||||||
import org.eclipse.jgit.api.errors.GitAPIException;
|
import org.eclipse.jgit.api.errors.GitAPIException;
|
||||||
|
import org.eclipse.jgit.errors.RepositoryNotFoundException;
|
||||||
|
import org.eclipse.jgit.lib.Ref;
|
||||||
|
import org.eclipse.jgit.merge.MergeStrategy;
|
||||||
|
import org.eclipse.jgit.transport.RemoteConfig;
|
||||||
|
import org.eclipse.jgit.transport.URIish;
|
||||||
import org.springframework.context.ApplicationEventPublisher;
|
import org.springframework.context.ApplicationEventPublisher;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.lang.NonNull;
|
import org.springframework.lang.NonNull;
|
||||||
|
@ -32,7 +39,9 @@ import run.halo.app.utils.FilenameUtils;
|
||||||
import run.halo.app.utils.HaloUtils;
|
import run.halo.app.utils.HaloUtils;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
@ -460,6 +469,87 @@ public class ThemeServiceImpl implements ThemeService {
|
||||||
eventPublisher.publishEvent(new ThemeUpdatedEvent(this));
|
eventPublisher.publishEvent(new ThemeUpdatedEvent(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ThemeProperty update(String themeId) {
|
||||||
|
Assert.hasText(themeId, "Theme id must not be blank");
|
||||||
|
|
||||||
|
ThemeProperty updatingTheme = getThemeOfNonNullBy(themeId);
|
||||||
|
|
||||||
|
try {
|
||||||
|
pullFromGit(updatingTheme);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new ThemeUpdateException("主题更新失败!您与主题作者可能同时更改了同一个文件,您也可以尝试删除主题并重新拉取最新的主题", e).setErrorData(themeId);
|
||||||
|
}
|
||||||
|
|
||||||
|
eventPublisher.publishEvent(new ThemeUpdatedEvent(this));
|
||||||
|
|
||||||
|
return getThemeOfNonNullBy(themeId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void pullFromGit(@NonNull ThemeProperty themeProperty) throws IOException, GitAPIException, URISyntaxException {
|
||||||
|
Assert.notNull(themeProperty, "Theme property must not be null");
|
||||||
|
|
||||||
|
// Get branch
|
||||||
|
String branch = StringUtils.isBlank(themeProperty.getBranch()) ?
|
||||||
|
DEFAULT_REMOTE_BRANCH : themeProperty.getBranch();
|
||||||
|
|
||||||
|
File themeFolder = Paths.get(themeProperty.getThemePath()).toFile();
|
||||||
|
// Open the theme path
|
||||||
|
Git git;
|
||||||
|
|
||||||
|
try {
|
||||||
|
git = Git.open(themeFolder);
|
||||||
|
} catch (RepositoryNotFoundException e) {
|
||||||
|
// Repository is not initialized
|
||||||
|
git = Git.init().setDirectory(themeFolder).call();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Force to set remote name
|
||||||
|
git.remoteRemove().setRemoteName(THEME_PROVIDER_REMOTE_NAME).call();
|
||||||
|
RemoteConfig remoteConfig = git.remoteAdd()
|
||||||
|
.setName(THEME_PROVIDER_REMOTE_NAME)
|
||||||
|
.setUri(new URIish(themeProperty.getRepo()))
|
||||||
|
.call();
|
||||||
|
|
||||||
|
// Add all changes
|
||||||
|
git.add()
|
||||||
|
.addFilepattern(".")
|
||||||
|
.call();
|
||||||
|
// Commit the changes
|
||||||
|
git.commit().setMessage("Commit by halo automatically").call();
|
||||||
|
|
||||||
|
// Check out to specified branch
|
||||||
|
if (!StringUtils.equalsIgnoreCase(branch, git.getRepository().getBranch())) {
|
||||||
|
boolean present = git.branchList()
|
||||||
|
.call()
|
||||||
|
.stream()
|
||||||
|
.map(Ref::getName)
|
||||||
|
.anyMatch(name -> StringUtils.equalsIgnoreCase(name, branch));
|
||||||
|
|
||||||
|
git.checkout()
|
||||||
|
.setCreateBranch(true)
|
||||||
|
.setForced(!present)
|
||||||
|
.setName(branch)
|
||||||
|
.call();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pull with rebasing
|
||||||
|
PullResult pullResult = git.pull()
|
||||||
|
.setRemote(remoteConfig.getName())
|
||||||
|
.setRemoteBranchName(branch)
|
||||||
|
.setRebase(true)
|
||||||
|
.call();
|
||||||
|
|
||||||
|
if (!pullResult.isSuccessful()) {
|
||||||
|
log.debug("Rebase result: [{}]", pullResult.getRebaseResult());
|
||||||
|
log.debug("Merge result: [{}]", pullResult.getMergeResult());
|
||||||
|
|
||||||
|
throw new ThemeUpdateException("拉取失败!您与主题作者可能同时更改了同一个文件");
|
||||||
|
}
|
||||||
|
// Close git
|
||||||
|
git.close();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clones theme from git.
|
* Clones theme from git.
|
||||||
*
|
*
|
||||||
|
|
|
@ -64,7 +64,7 @@ public class FileUtils {
|
||||||
*
|
*
|
||||||
* @param deletingPath deleting path must not be null
|
* @param deletingPath deleting path must not be null
|
||||||
*/
|
*/
|
||||||
public static void deleteFolder(Path deletingPath) throws IOException {
|
public static void deleteFolder(@NonNull Path deletingPath) throws IOException {
|
||||||
Assert.notNull(deletingPath, "Deleting path must not be null");
|
Assert.notNull(deletingPath, "Deleting path must not be null");
|
||||||
|
|
||||||
log.debug("Deleting [{}]", deletingPath);
|
log.debug("Deleting [{}]", deletingPath);
|
||||||
|
@ -249,9 +249,9 @@ public class FileUtils {
|
||||||
/**
|
/**
|
||||||
* Deletes folder quietly.
|
* Deletes folder quietly.
|
||||||
*
|
*
|
||||||
* @param deletingPath deleting path must not be null
|
* @param deletingPath deleting path
|
||||||
*/
|
*/
|
||||||
public static void deleteFolderQuietly(@NonNull Path deletingPath) {
|
public static void deleteFolderQuietly(@Nullable Path deletingPath) {
|
||||||
try {
|
try {
|
||||||
if (deletingPath != null) {
|
if (deletingPath != null) {
|
||||||
FileUtils.deleteFolder(deletingPath);
|
FileUtils.deleteFolder(deletingPath);
|
||||||
|
|
|
@ -44,8 +44,8 @@ spring:
|
||||||
open-in-view: false
|
open-in-view: false
|
||||||
servlet:
|
servlet:
|
||||||
multipart:
|
multipart:
|
||||||
max-file-size: 10MB
|
max-file-size: 10240MB
|
||||||
max-request-size: 10MB
|
max-request-size: 10240MB
|
||||||
|
|
||||||
cache:
|
cache:
|
||||||
type: none
|
type: none
|
||||||
|
|
|
@ -5,5 +5,6 @@ author:
|
||||||
website: https://www.caicai.me
|
website: https://www.caicai.me
|
||||||
description: A other Halo theme
|
description: A other Halo theme
|
||||||
logo: https://avatars1.githubusercontent.com/u/1811819?s=460&v=4
|
logo: https://avatars1.githubusercontent.com/u/1811819?s=460&v=4
|
||||||
|
website: https://github.com/halo-dev/halo-theme-anatole
|
||||||
repo: https://github.com/halo-dev/halo-theme-anatole
|
repo: https://github.com/halo-dev/halo-theme-anatole
|
||||||
version: 1.0
|
version: 1.0
|
|
@ -1,15 +1,23 @@
|
||||||
package run.halo.app.utils;
|
package run.halo.app.utils;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.eclipse.jgit.api.Git;
|
import org.eclipse.jgit.api.Git;
|
||||||
import org.eclipse.jgit.api.Status;
|
import org.eclipse.jgit.api.Status;
|
||||||
import org.eclipse.jgit.api.errors.GitAPIException;
|
import org.eclipse.jgit.api.errors.GitAPIException;
|
||||||
import org.eclipse.jgit.errors.RepositoryNotFoundException;
|
import org.eclipse.jgit.errors.RepositoryNotFoundException;
|
||||||
|
import org.eclipse.jgit.transport.RemoteConfig;
|
||||||
|
import org.eclipse.jgit.transport.URIish;
|
||||||
|
import org.junit.AfterClass;
|
||||||
|
import org.junit.BeforeClass;
|
||||||
import org.junit.Ignore;
|
import org.junit.Ignore;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Git test.
|
* Git test.
|
||||||
|
@ -17,17 +25,23 @@ import java.nio.file.Path;
|
||||||
* @author johnniang
|
* @author johnniang
|
||||||
* @date 19-5-21
|
* @date 19-5-21
|
||||||
*/
|
*/
|
||||||
@Ignore
|
@Slf4j
|
||||||
public class GitTest {
|
public class GitTest {
|
||||||
|
|
||||||
private final Path tempPath;
|
private static Path tempPath;
|
||||||
|
|
||||||
public GitTest() throws IOException {
|
@BeforeClass
|
||||||
|
public static void setUp() throws IOException {
|
||||||
tempPath = Files.createTempDirectory("git-test");
|
tempPath = Files.createTempDirectory("git-test");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@AfterClass
|
||||||
|
public static void destroy() throws IOException {
|
||||||
|
FileUtils.deleteFolder(tempPath);
|
||||||
|
}
|
||||||
|
|
||||||
@Test(expected = RepositoryNotFoundException.class)
|
@Test(expected = RepositoryNotFoundException.class)
|
||||||
public void openTest() throws IOException {
|
public void openFailureTest() throws IOException {
|
||||||
Git.open(tempPath.toFile());
|
Git.open(tempPath.toFile());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,17 +51,29 @@ public class GitTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void statusTest() throws GitAPIException {
|
public void statusSuccessfulTest() throws GitAPIException {
|
||||||
Git git = Git.init().setDirectory(tempPath.toFile()).call();
|
Git git = Git.init().setDirectory(tempPath.toFile()).call();
|
||||||
git.status().call();
|
Status status = git.status().call();
|
||||||
|
log.debug("Status missing: [{}]", status.getMissing());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
public void remoteAddTest() throws GitAPIException, URISyntaxException {
|
||||||
|
Git git = Git.init().setDirectory(tempPath.toFile()).call();
|
||||||
|
git.remoteRemove().setRemoteName("theme-provider").call();
|
||||||
|
git.remoteAdd().setName("theme-provider").setUri(new URIish("https://github.com/halo-dev/halo-theme-pinghsu.git")).call();
|
||||||
|
List<RemoteConfig> remoteConfigs = git.remoteList().call();
|
||||||
|
remoteConfigs.forEach(remoteConfig -> log.debug("name: [{}], url: [{}]", remoteConfig.getName(), remoteConfig.getURIs()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Ignore
|
||||||
public void cloneTest() throws GitAPIException {
|
public void cloneTest() throws GitAPIException {
|
||||||
cloneRepository();
|
cloneRepository();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@Ignore
|
||||||
public void pullTest() throws GitAPIException {
|
public void pullTest() throws GitAPIException {
|
||||||
Git git = cloneRepository();
|
Git git = cloneRepository();
|
||||||
git.pull().call();
|
git.pull().call();
|
||||||
|
|
Loading…
Reference in New Issue