mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
GitRepository: read and update local and remote branches.
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright 2000-2011 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* 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 git4idea.branch;
|
||||
|
||||
import git4idea.GitBranch;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Storage for local, remote and current branches.
|
||||
* The reason of creating this special collection is that
|
||||
* in the terms of performance, they are detected by {@link git4idea.repo.GitRepositoryReader} at once;
|
||||
* and also usually both sets of branches are needed by components, but are treated differently,
|
||||
* so it is more convenient to have them separated, but in a single container.
|
||||
* </p>
|
||||
*
|
||||
* @author Kirill Likhodedov
|
||||
*/
|
||||
public final class GitBranchesCollection {
|
||||
|
||||
public static final GitBranchesCollection EMPTY = new GitBranchesCollection(null, new HashSet<GitBranch>(), new HashSet<GitBranch>());
|
||||
|
||||
private final Set<GitBranch> myLocalBranches;
|
||||
private final Set<GitBranch> myRemoteBranches;
|
||||
private final GitBranch myCurrentBranch;
|
||||
|
||||
public GitBranchesCollection(@Nullable GitBranch currentBranch, @NotNull Set<GitBranch> localBranches, @NotNull Set<GitBranch> remoteBranches) {
|
||||
myCurrentBranch = currentBranch;
|
||||
myRemoteBranches = remoteBranches;
|
||||
myLocalBranches = localBranches;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Set<GitBranch> getLocalBranches() {
|
||||
return myLocalBranches;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Set<GitBranch> getRemoteBranches() {
|
||||
return myRemoteBranches;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return currently active branch.
|
||||
* Current branch may be <code>null</code> in the case of detached head,
|
||||
*/
|
||||
@Nullable
|
||||
public GitBranch getCurrentBranch() {
|
||||
return myCurrentBranch;
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@ import com.intellij.util.messages.MessageBus;
|
||||
import com.intellij.util.messages.MessageBusConnection;
|
||||
import com.intellij.util.messages.Topic;
|
||||
import git4idea.GitBranch;
|
||||
import git4idea.branch.GitBranchesCollection;
|
||||
import git4idea.status.GitUntrackedFilesHolder;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -64,11 +65,13 @@ public final class GitRepository implements Disposable {
|
||||
|
||||
private volatile State myState;
|
||||
private volatile String myCurrentRevision;
|
||||
|
||||
private volatile GitBranch myCurrentBranch;
|
||||
private volatile GitBranchesCollection myBranches = GitBranchesCollection.EMPTY;
|
||||
|
||||
private final ReadWriteLock STATE_LOCK = new ReentrantReadWriteLock();
|
||||
private final ReadWriteLock CUR_REV_LOCK = new ReentrantReadWriteLock();
|
||||
private final ReadWriteLock CUR_BRANCH_LOCK = new ReentrantReadWriteLock();
|
||||
private final ReadWriteLock BRANCHES_LOCK = new ReentrantReadWriteLock();
|
||||
|
||||
/**
|
||||
* Current state of the repository.
|
||||
@@ -112,6 +115,11 @@ public final class GitRepository implements Disposable {
|
||||
repository.updateCurrentBranch();
|
||||
}
|
||||
},
|
||||
BRANCHES {
|
||||
@Override void update(GitRepository repository) {
|
||||
repository.updateBranchList();
|
||||
}
|
||||
},
|
||||
ALL_CURRENT {
|
||||
@Override void update(GitRepository repository) {
|
||||
STATE.update(repository);
|
||||
@@ -122,6 +130,7 @@ public final class GitRepository implements Disposable {
|
||||
ALL {
|
||||
@Override void update(GitRepository repository) {
|
||||
ALL_CURRENT.update(repository);
|
||||
BRANCHES.update(repository);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -220,6 +229,17 @@ public final class GitRepository implements Disposable {
|
||||
public boolean isOnBranch() {
|
||||
return getState() != State.DETACHED && getState() != State.REBASING;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public GitBranchesCollection getBranches() {
|
||||
try {
|
||||
BRANCHES_LOCK.readLock().lock();
|
||||
return myBranches;
|
||||
}
|
||||
finally {
|
||||
BRANCHES_LOCK.readLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public void addListener(GitRepositoryChangeListener listener) {
|
||||
MessageBusConnection connection = myMessageBus.connect();
|
||||
@@ -280,6 +300,18 @@ public final class GitRepository implements Disposable {
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
private void updateBranchList() {
|
||||
GitBranchesCollection branches = myReader.readBranches();
|
||||
try {
|
||||
BRANCHES_LOCK.writeLock().lock();
|
||||
myBranches = branches;
|
||||
}
|
||||
finally {
|
||||
BRANCHES_LOCK.writeLock().unlock();
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
private void notifyListeners() {
|
||||
myMessageBus.syncPublisher(GIT_REPO_CHANGE).repositoryChanged();
|
||||
|
||||
@@ -20,6 +20,7 @@ import com.intellij.openapi.util.io.FileUtil;
|
||||
import com.intellij.openapi.vfs.VfsUtil;
|
||||
import com.intellij.util.Processor;
|
||||
import git4idea.GitBranch;
|
||||
import git4idea.branch.GitBranchesCollection;
|
||||
import git4idea.merge.GitMergeUtil;
|
||||
import git4idea.rebase.GitRebaseUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -30,8 +31,11 @@ import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@@ -53,12 +57,15 @@ class GitRepositoryReader {
|
||||
private static Pattern PACKED_REFS_TAGREF_LINE = Pattern.compile("\\^[0-9a-fA-F]+"); // tag reference in .git/packed-refs
|
||||
|
||||
private static final String REFS_HEADS_PREFIX = "refs/heads/";
|
||||
private static final String REFS_REMOTES_PREFIX = "refs/remotes/";
|
||||
private static final int IO_RETRIES = 3; // number of retries before fail if an IOException happens during file read.
|
||||
|
||||
private final GitRepository myRepository;
|
||||
private final File myGitDir; // .git/
|
||||
private final File myHeadFile; // .git/HEAD
|
||||
private final File myRefsHeadsDir; // .git/refs/heads/
|
||||
private final File myGitDir; // .git/
|
||||
private final File myHeadFile; // .git/HEAD
|
||||
private final File myRefsHeadsDir; // .git/refs/heads/
|
||||
private final File myRefsRemotesDir; // .git/refs/remotes/
|
||||
private final File myPackedRefsFile; // .git/packed-refs
|
||||
|
||||
GitRepositoryReader(@NotNull GitRepository repository) {
|
||||
myRepository = repository;
|
||||
@@ -67,6 +74,8 @@ class GitRepositoryReader {
|
||||
myHeadFile = new File(myGitDir, "HEAD");
|
||||
assertFileExists(myHeadFile, ".git/HEAD file not found in " + myRepository.getRoot());
|
||||
myRefsHeadsDir = new File(new File(myGitDir, "refs"), "heads");
|
||||
myRefsRemotesDir = new File(new File(myGitDir, "refs"), "remotes");
|
||||
myPackedRefsFile = new File(myGitDir, "packed-refs");
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -97,7 +106,7 @@ class GitRepositoryReader {
|
||||
|
||||
// look in /refs/heads/<branch name>
|
||||
File branchFile = null;
|
||||
for (Map.Entry<String, File> entry : getLocalBranches().entrySet()) {
|
||||
for (Map.Entry<String, File> entry : readLocalBranches().entrySet()) {
|
||||
if (entry.getKey().equals(head.ref)) {
|
||||
branchFile = entry.getValue();
|
||||
}
|
||||
@@ -167,8 +176,7 @@ class GitRepositoryReader {
|
||||
*/
|
||||
@Nullable
|
||||
private String findBranchRevisionInPackedRefs(final String ref) {
|
||||
final File packedRefs = new File(myGitDir, "packed-refs");
|
||||
if (!packedRefs.exists()) {
|
||||
if (!myPackedRefsFile.exists()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -177,12 +185,23 @@ class GitRepositoryReader {
|
||||
public String call() throws Exception {
|
||||
BufferedReader reader = null;
|
||||
try {
|
||||
reader = new BufferedReader(new FileReader(packedRefs));
|
||||
reader = new BufferedReader(new FileReader(myPackedRefsFile));
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
String hash = findRefHashInPackedRefsLine(line, ref);
|
||||
if (hash != null) {
|
||||
return hash;
|
||||
final AtomicReference<String> hashRef = new AtomicReference<String>();
|
||||
parsePackedRefsLine(line, new PackedRefsLineResultHandler() {
|
||||
@Override public void handleResult(String hash, String branchName) {
|
||||
if (hash == null || branchName == null) {
|
||||
return;
|
||||
}
|
||||
if (branchName.endsWith(ref)) {
|
||||
hashRef.set(hash);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (hashRef.get() != null) {
|
||||
return hashRef.get();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
@@ -193,14 +212,14 @@ class GitRepositoryReader {
|
||||
}
|
||||
}
|
||||
}
|
||||
}, packedRefs);
|
||||
}, myPackedRefsFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the list of local branches in this Git repository.
|
||||
* key is the branch name, value is the file.
|
||||
*/
|
||||
private Map<String, File> getLocalBranches() {
|
||||
private Map<String, File> readLocalBranches() {
|
||||
final Map<String, File> branches = new HashMap<String, File>();
|
||||
FileUtil.processFilesRecursively(myRefsHeadsDir, new Processor<File>() {
|
||||
@Override
|
||||
@@ -214,28 +233,96 @@ class GitRepositoryReader {
|
||||
return branches;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static String findRefHashInPackedRefsLine(String line, String ref) {
|
||||
if (line.startsWith("#")) { // ignoring comments
|
||||
return null;
|
||||
/**
|
||||
* @return all branches in this repository. local/remote/active information is stored in branch objects themselves.
|
||||
*/
|
||||
GitBranchesCollection readBranches() {
|
||||
Set<GitBranch> localBranches = readUnpackedLocalBranches();
|
||||
Set<GitBranch> remoteBranches = readUnpackedRemoteBranches();
|
||||
GitBranchesCollection packedBranches = readPackedBranches();
|
||||
localBranches.addAll(packedBranches.getLocalBranches());
|
||||
remoteBranches.addAll(packedBranches.getRemoteBranches());
|
||||
|
||||
// note that even the active branch may be packed. So at first we collect branches, then we find the active.
|
||||
GitBranch currentBranch = readCurrentBranch();
|
||||
markActiveBranch(localBranches, currentBranch);
|
||||
|
||||
return new GitBranchesCollection(currentBranch, localBranches, remoteBranches);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the 'active' flag to the current branch if it is contained in the specified collection.
|
||||
* @param branches branches to be walked through.
|
||||
* @param currentBranch current branch.
|
||||
*/
|
||||
private static void markActiveBranch(@NotNull Set<GitBranch> branches, @Nullable GitBranch currentBranch) {
|
||||
if (currentBranch == null) {
|
||||
return;
|
||||
}
|
||||
if (PACKED_REFS_TAGREF_LINE.matcher(line).matches()) { // ignoring the hash which an annotated tag above points to
|
||||
return null;
|
||||
}
|
||||
Matcher matcher = PACKED_REFS_BRANCH_LINE.matcher(line);
|
||||
if (matcher.matches()) {
|
||||
String hash = matcher.group(1);
|
||||
String branch = matcher.group(2);
|
||||
if (branch.endsWith(ref)) {
|
||||
return hash;
|
||||
for (GitBranch branch : branches) {
|
||||
if (branch.getName().equals(currentBranch.getName())) {
|
||||
branch.setActive(true);
|
||||
}
|
||||
} else {
|
||||
LOG.info("Ignoring invalid packed-refs line: [" + line + "]");
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list of branches from refs/heads. active branch is not marked as active - the caller should do this.
|
||||
*/
|
||||
@NotNull
|
||||
private Set<GitBranch> readUnpackedLocalBranches() {
|
||||
Set<GitBranch> branches = new HashSet<GitBranch>();
|
||||
for (String branchName : readLocalBranches().keySet()) {
|
||||
branches.add(new GitBranch(branchName, false, false));
|
||||
}
|
||||
return branches;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list of branches from refs/remotes.
|
||||
*/
|
||||
private Set<GitBranch> readUnpackedRemoteBranches() {
|
||||
final Set<GitBranch> branches = new HashSet<GitBranch>();
|
||||
FileUtil.processFilesRecursively(myRefsRemotesDir, new Processor<File>() {
|
||||
@Override
|
||||
public boolean process(File file) {
|
||||
if (!file.isDirectory()) {
|
||||
final String relativePath = FileUtil.getRelativePath(myRefsRemotesDir, file);
|
||||
branches.add(new GitBranch(relativePath, false, true));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
return branches;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list of local and remote branches from packed-refs. Active branch is not marked as active.
|
||||
*/
|
||||
@NotNull
|
||||
private GitBranchesCollection readPackedBranches() {
|
||||
final Set<GitBranch> localBranches = new HashSet<GitBranch>();
|
||||
final Set<GitBranch> remoteBranches = new HashSet<GitBranch>();
|
||||
final String content = tryLoadFile(myPackedRefsFile);
|
||||
|
||||
for (String line : content.split("\n")) {
|
||||
parsePackedRefsLine(line, new PackedRefsLineResultHandler() {
|
||||
@Override public void handleResult(@Nullable String hash, @Nullable String branchName) {
|
||||
if (hash == null || branchName == null) {
|
||||
return;
|
||||
}
|
||||
if (branchName.startsWith(REFS_HEADS_PREFIX)) {
|
||||
localBranches.add(new GitBranch(branchName.substring(REFS_HEADS_PREFIX.length()), false, false));
|
||||
} else if (branchName.startsWith(REFS_REMOTES_PREFIX)) {
|
||||
remoteBranches.add(new GitBranch(branchName.substring(REFS_REMOTES_PREFIX.length()), false, true));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
return new GitBranchesCollection(null, localBranches, remoteBranches);
|
||||
}
|
||||
|
||||
|
||||
private static String readBranchFile(File branchFile) {
|
||||
String rev = tryLoadFile(branchFile);
|
||||
return rev.trim();
|
||||
@@ -297,6 +384,42 @@ class GitRepositoryReader {
|
||||
throw new GitRepoStateException("Couldn't load file " + fileToLoad, cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a line from the .git/packed-refs file.
|
||||
* Passes the parsed hash-branch pair to the resultHandler.
|
||||
* Comments, tags and incorrectly formatted lines are ignored, and (null, null) is passed to the handler then.
|
||||
* Using a special handler may seem to be an overhead, but it is to avoid code duplication in two methods that parse packed-refs.
|
||||
*/
|
||||
private static void parsePackedRefsLine(String line, PackedRefsLineResultHandler resultHandler) {
|
||||
line = line.trim();
|
||||
if (line.startsWith("#")) { // ignoring comments
|
||||
resultHandler.handleResult(null, null);
|
||||
return;
|
||||
}
|
||||
if (PACKED_REFS_TAGREF_LINE.matcher(line).matches()) { // ignoring the hash which an annotated tag above points to
|
||||
resultHandler.handleResult(null, null);
|
||||
return;
|
||||
}
|
||||
Matcher matcher = PACKED_REFS_BRANCH_LINE.matcher(line);
|
||||
if (matcher.matches()) {
|
||||
String hash = matcher.group(1);
|
||||
String branch = matcher.group(2);
|
||||
resultHandler.handleResult(hash, branch);
|
||||
} else {
|
||||
LOG.info("Ignoring invalid packed-refs line: [" + line + "]");
|
||||
resultHandler.handleResult(null, null);
|
||||
return;
|
||||
}
|
||||
resultHandler.handleResult(null, null);
|
||||
}
|
||||
|
||||
private interface PackedRefsLineResultHandler {
|
||||
void handleResult(@Nullable String hash, @Nullable String branchName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Container to hold two information items: current .git/HEAD value and is Git on branch.
|
||||
*/
|
||||
private static class Head {
|
||||
private final String ref;
|
||||
private final boolean isBranch;
|
||||
@@ -305,7 +428,7 @@ class GitRepositoryReader {
|
||||
isBranch = branch;
|
||||
this.ref = ref;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user