/*
* Copyright 2013 Square, Inc.
* No license information provided
*
* Copyright (c) 2015-2015 Vladimir Schneider <vladimir.schneider@gmail.com>, all rights reserved.
*
* This code is based on code from https://github.com/jawspeak/intellij-plugin-copy-and-open-github-url
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
package com.vladsch.idea.multimarkdown.util;
import com.google.common.annotations.VisibleForTesting;
import com.intellij.openapi.vfs.VirtualFile;
import org.apache.log4j.Logger;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
*
*/
public class GitHubVcsRoot {
private static final Logger logger = org.apache.log4j.Logger.getLogger(GitHubVcsRoot.class);
final private static Pattern INI_CATEGORY = Pattern.compile("\\[\\s*(\\w+)[\\s'\"]+(\\w+)[\\s'\"]+\\]");
final private static Pattern URL_VALUE = Pattern.compile("\\s*url\\s*=\\s*([^\\s]*)\\.git");
final protected static String GIT_CONFIG = "config";
@NotNull private final String gitHubBaseUrl;
@NotNull private final String basePath;
@NotNull private final String projectBasePath;
private final boolean isWiki;
@VisibleForTesting
protected GitHubVcsRoot(@NotNull String gitHubBaseUrl, @NotNull String basePath) {
// strip out username if the url contains @ from URL
// regex: ^(https?://)(?:[^@]*\Q@\E)(.*)$
// replace with $1$2
String _gitHubBaseUrl = gitHubBaseUrl;
int atPos = _gitHubBaseUrl.indexOf('@', 7);
if ((_gitHubBaseUrl.startsWith("http://") || _gitHubBaseUrl.startsWith("https://")) && atPos > 0) {
int prefixPos = _gitHubBaseUrl.startsWith("http://") ? "http://".length() : "https://".length();
_gitHubBaseUrl = _gitHubBaseUrl.substring(0, prefixPos) + _gitHubBaseUrl.substring(atPos+1);
//logger.info("cleaned username from url " + gitHubBaseUrl + " -> " + _gitHubBaseUrl);
}
this.gitHubBaseUrl = StringUtilKt.suffixWith(_gitHubBaseUrl, '/');
this.basePath = StringUtilKt.suffixWith(basePath, '/');
this.isWiki = new FileRef(this.basePath + "Home.md").isUnderWikiDir();
this.projectBasePath = this.isWiki ? new PathInfo(this.basePath).getPath() : this.basePath;
}
public boolean isWiki() {
return isWiki;
}
@NotNull
public String getBasePath() {
return basePath;
}
@NotNull
public String getMainRepoBaseDir() {
return projectBasePath;
}
@NotNull
public String getBaseUrl() {
return gitHubBaseUrl;
}
@Nullable
public String getRelativePath(@Nullable String path) {
if (path != null && path.startsWith(basePath)) {
return path.substring(basePath.length());
}
return null;
}
@NotNull
public String urlForVcsRemote(String relativeFilePath, @Nullable String branchOrTag, @Nullable String gitHubLink) {
return urlForVcsRemote(relativeFilePath, null, branchOrTag, gitHubLink);
}
@Nullable
public String urlForVcsRemote(@NotNull VirtualFile virtualFile, boolean withExtension, @Nullable String anchor, @Nullable String branchOrTag, @Nullable String gitHubLink) {
PathInfo pathInfo = new PathInfo(virtualFile.getPath());
String relativePath = getRelativePath(withExtension ? pathInfo.getFilePath() : pathInfo.getFilePathNoExt());
return relativePath == null ? null : urlForVcsRemote(relativePath, anchor, branchOrTag, gitHubLink);
}
@Nullable
public String urlForVcsRemote(@NotNull FileRef fileRef, boolean withExtension, @Nullable String anchor, @Nullable String branchOrTag, @Nullable String gitHubLink) {
String relativePath = !fileRef.isUnderWikiDir() || withExtension ? getRelativePath(fileRef.getFilePath()) : fileRef.getFileNameNoExt();
if (isWiki && relativePath != null && relativePath.equals("Home")) relativePath = "";
return relativePath == null ? null : urlForVcsRemote(relativePath, anchor, branchOrTag, gitHubLink);
}
public String urlForVcsRemote(@NotNull String relativeFilePath, @Nullable String anchor, @Nullable String branchOrTag, @Nullable String gitHubLink) {
if (isWiki() && relativeFilePath.startsWith("../../wiki")) {
relativeFilePath = StringUtilKt.removeStart(relativeFilePath, "../../wiki");
}
if (branchOrTag == null || branchOrTag.isEmpty()) branchOrTag = "master";
if (gitHubLink == null || gitHubLink.isEmpty()) gitHubLink = "blob";
return gitHubBaseUrl + (isWiki() ? "wiki/" : gitHubLink + "/" + branchOrTag + "/") + LinkRef.urlEncode(StringUtilKt.removeStart(relativeFilePath, "./")) + StringUtilKt.prefixWith(anchor, '#', false);
}
@Nullable
protected static String getGitPath(@NotNull String filePath) {
PathInfo filePathInfo = new PathInfo(filePath).append(".git");
File gitFile = new File(filePathInfo.getFilePath());
String gitPath = null;
if (gitFile.exists()) {
if (gitFile.isFile()) {
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader(gitFile));
String line;
while ((line = reader.readLine()) != null) {
// gitdir: ../.git/modules/laravel-translation-manager.isWiki
if (line.startsWith("gitdir:")) {
line = line.substring("gitdir:".length()).trim();
PathInfo lineInfo = new PathInfo(line);
PathInfo configInfo = lineInfo.isRelative() ? filePathInfo.append(line) : lineInfo;
gitPath = configInfo.getFilePath();
break;
}
}
} catch (IOException ignored) {
logger.info("Could not read " + gitFile, ignored);
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException ignored) {
}
}
}
} else {
return filePathInfo.getFilePath();
}
}
return gitPath;
}
@Nullable
protected static String getBaseUrl(@NotNull File gitConfigFile) {
String baseUrl = null;
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader(gitConfigFile));
String line;
boolean inRemoteOriginSection = false;
while ((line = reader.readLine()) != null) {
if (line.matches("\\s*#")) continue;
Matcher matcher = INI_CATEGORY.matcher(line);
if (matcher.matches()) {
inRemoteOriginSection = "remote".equals(matcher.group(1))
&& "origin".equals(matcher.group(2));
continue;
}
matcher = URL_VALUE.matcher(line);
if (inRemoteOriginSection && matcher.matches()) {
baseUrl = "https://" + matcher.group(1)
.replaceAll("git://|git@|https://", "")
.replaceAll(":", "/");
if (baseUrl.endsWith(PathInfo.WIKI_HOME_DIR_EXTENSION)) {
FileRef baseUrlInfo = new FileRef(baseUrl);
baseUrl = baseUrlInfo.getFilePathNoExt();
}
break;
}
}
} catch (IOException ignored) {
logger.info("No remote origin in " + gitConfigFile, ignored);
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException ignored) {
}
}
}
return baseUrl;
}
@Nullable
public static GitHubVcsRoot getGitHubVcsRoot(@Nullable String path, @Nullable String basePath) {
if (path == null || basePath == null) return null;
String nextPath = path;
do {
PathInfo pathInfo = new PathInfo(nextPath);
String gitPath = getGitPath(pathInfo.getFilePath());
if (gitPath != null) {
File gitConfigFile = new File(gitPath, GIT_CONFIG);
if (gitConfigFile.exists() && gitConfigFile.isFile()) {
String baseUrl = getBaseUrl(gitConfigFile);
if (baseUrl != null) {
return new GitHubVcsRoot(baseUrl, pathInfo.getFilePath());
}
// this sub-module does not have a remote.
return null;
}
}
nextPath = pathInfo.getPath();
} while (!nextPath.isEmpty() && !nextPath.equals("/") && !nextPath.equalsIgnoreCase(basePath));
return null;
}
}