2 * Copyright 2000-2011 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package git4idea.commands;
18 import com.intellij.openapi.diagnostic.Logger;
19 import com.intellij.openapi.project.Project;
20 import com.intellij.openapi.util.Key;
21 import com.intellij.openapi.util.text.StringUtil;
22 import com.intellij.openapi.vcs.VcsException;
23 import com.intellij.openapi.vfs.VirtualFile;
24 import com.intellij.util.ExceptionUtil;
25 import com.intellij.vcsUtil.VcsFileUtil;
26 import git4idea.GitBranch;
27 import git4idea.push.GitPushSpec;
28 import git4idea.repo.GitRemote;
29 import git4idea.repo.GitRepository;
30 import org.jetbrains.annotations.NotNull;
31 import org.jetbrains.annotations.Nullable;
35 import java.util.concurrent.atomic.AtomicBoolean;
36 import java.util.concurrent.atomic.AtomicInteger;
39 * Collection of common native Git commands.
41 * @author Kirill Likhodedov
45 private static final Logger LOG = Logger.getInstance(Git.class);
51 * Calls 'git init' on the specified directory.
52 * // TODO use common format
54 public static void init(Project project, VirtualFile root) throws VcsException {
55 GitSimpleHandler h = new GitSimpleHandler(project, root, GitCommand.INIT);
59 if (!h.errors().isEmpty()) {
60 throw h.errors().get(0);
65 * <p>Queries Git for the unversioned files in the given paths.</p>
67 * <b>Note:</b> this method doesn't check for ignored files. You have to check if the file is ignored afterwards, if needed.
70 * @param files files that are to be checked for the unversioned files among them.
71 * <b>Pass <code>null</code> to query the whole repository.</b>
72 * @return Unversioned files from the given scope.
75 public static Set<VirtualFile> untrackedFiles(@NotNull Project project,
76 @NotNull VirtualFile root,
77 @Nullable Collection<VirtualFile> files) throws VcsException {
78 final Set<VirtualFile> untrackedFiles = new HashSet<VirtualFile>();
81 untrackedFiles.addAll(untrackedFilesNoChunk(project, root, null));
83 for (List<String> relativePaths : VcsFileUtil.chunkFiles(root, files)) {
84 untrackedFiles.addAll(untrackedFilesNoChunk(project, root, relativePaths));
88 return untrackedFiles;
91 // relativePaths are guaranteed to fit into command line length limitations.
93 private static Collection<VirtualFile> untrackedFilesNoChunk(@NotNull Project project,
94 @NotNull VirtualFile root,
95 @Nullable List<String> relativePaths)
97 final Set<VirtualFile> untrackedFiles = new HashSet<VirtualFile>();
98 GitSimpleHandler h = new GitSimpleHandler(project, root, GitCommand.LS_FILES);
101 h.addParameters("--exclude-standard", "--others", "-z");
103 if (relativePaths != null) {
104 h.addParameters(relativePaths);
107 final String output = h.run();
108 if (StringUtil.isEmptyOrSpaces(output)) {
109 return untrackedFiles;
112 for (String relPath : output.split("\u0000")) {
113 VirtualFile f = root.findFileByRelativePath(relPath);
115 // files was created on disk, but VirtualFile hasn't yet been created,
116 // when the GitChangeProvider has already been requested about changes.
117 LOG.info(String.format("VirtualFile for path [%s] is null", relPath));
119 untrackedFiles.add(f);
123 return untrackedFiles;
127 public static GitCommandResult clone(@NotNull Project project, @NotNull File parentDirectory, @NotNull String url, @NotNull String clonedDirectoryName) {
128 GitLineHandlerPasswordRequestAware handler = new GitLineHandlerPasswordRequestAware(project, parentDirectory, GitCommand.CLONE);
129 handler.addParameters(url);
130 handler.addParameters(clonedDirectoryName);
131 return run(handler, true);
135 * {@code git checkout <reference>} <br/>
136 * {@code git checkout -b <newBranch> <reference>}
138 public static GitCommandResult checkout(@NotNull GitRepository repository,
139 @NotNull String reference,
140 @Nullable String newBranch,
142 @NotNull GitLineHandlerListener... listeners) {
143 final GitLineHandler h = new GitLineHandler(repository.getProject(), repository.getRoot(), GitCommand.CHECKOUT);
146 h.addParameters("--force");
148 if (newBranch == null) { // simply checkout
149 h.addParameters(reference);
151 else { // checkout reference as new branch
152 h.addParameters("-b", newBranch, reference);
154 for (GitLineHandlerListener listener : listeners) {
155 h.addLineListener(listener);
161 * {@code git checkout -b <branchName>}
163 public static GitCommandResult checkoutNewBranch(@NotNull GitRepository repository, @NotNull String branchName,
164 @Nullable GitLineHandlerListener listener) {
165 final GitLineHandler h = new GitLineHandler(repository.getProject(), repository.getRoot(), GitCommand.CHECKOUT);
167 h.addParameters("-b");
168 h.addParameters(branchName);
169 if (listener != null) {
170 h.addLineListener(listener);
175 public static GitCommandResult createNewTag(@NotNull GitRepository repository, @NotNull String tagName,
176 @Nullable GitLineHandlerListener listener, String reference) {
177 final GitLineHandler h = new GitLineHandler(repository.getProject(), repository.getRoot(), GitCommand.TAG);
179 h.addParameters(tagName);
180 if (reference != null && ! reference.isEmpty()) {
181 h.addParameters(reference);
183 if (listener != null) {
184 h.addLineListener(listener);
190 * {@code git branch -d <reference>} or {@code git branch -D <reference>}
192 public static GitCommandResult branchDelete(@NotNull GitRepository repository,
193 @NotNull String branchName,
195 @NotNull GitLineHandlerListener... listeners) {
196 final GitLineHandler h = new GitLineHandler(repository.getProject(), repository.getRoot(), GitCommand.BRANCH);
198 h.addParameters(force ? "-D" : "-d");
199 h.addParameters(branchName);
200 for (GitLineHandlerListener listener : listeners) {
201 h.addLineListener(listener);
207 * Get branches containing the commit.
208 * {@code git branch --contains <commit>}
211 public static GitCommandResult branchContains(@NotNull GitRepository repository, @NotNull String commit) {
212 final GitLineHandler h = new GitLineHandler(repository.getProject(), repository.getRoot(), GitCommand.BRANCH);
213 h.addParameters("--contains", commit);
218 * Create branch without checking it out.
219 * {@code git branch <branchName>}
222 public static GitCommandResult branchCreate(@NotNull GitRepository repository, @NotNull String branchName) {
223 final GitLineHandler h = new GitLineHandler(repository.getProject(), repository.getRoot(), GitCommand.BRANCH);
224 h.addParameters(branchName);
229 * Returns the last (tip) commit on the given branch.<br/>
230 * {@code git rev-list -1 <branchName>}
232 public static GitCommandResult tip(@NotNull GitRepository repository, @NotNull String branchName) {
233 final GitLineHandler h = new GitLineHandler(repository.getProject(), repository.getRoot(), GitCommand.REV_LIST);
234 h.addParameters("-1");
235 h.addParameters(branchName);
239 public static GitCommandResult push(@NotNull GitRepository repository, @NotNull GitPushSpec pushSpec, @NotNull GitLineHandlerListener... listeners) {
240 final GitLineHandlerPasswordRequestAware h = new GitLineHandlerPasswordRequestAware(repository.getProject(), repository.getRoot(), GitCommand.PUSH);
243 for (GitLineHandlerListener listener : listeners) {
244 h.addLineListener(listener);
246 if (!pushSpec.isPushAll()) {
247 GitRemote remote = pushSpec.getRemote();
248 LOG.assertTrue(remote != null, "Remote can't be null: " + pushSpec);
249 h.addParameters(remote.getName());
250 GitBranch remoteBranch = pushSpec.getDest();
251 String destination = remoteBranch.getName().replaceFirst(remote.getName() + "/", "");
252 h.addParameters(pushSpec.getSource().getName() + ":" + destination);
257 private static GitCommandResult run(@NotNull GitLineHandler handler) {
258 return run(handler, false);
262 * Runs the given {@link GitLineHandler} in the current thread and returns the {@link GitCommandResult}.
264 private static GitCommandResult run(@NotNull GitLineHandler handler, boolean remote) {
265 handler.setNoSSH(!remote);
267 final List<String> errorOutput = new ArrayList<String>();
268 final List<String> output = new ArrayList<String>();
269 final AtomicInteger exitCode = new AtomicInteger();
270 final AtomicBoolean startFailed = new AtomicBoolean();
272 handler.addLineListener(new GitLineHandlerListener() {
273 @Override public void onLineAvailable(String line, Key outputType) {
275 errorOutput.add(line);
281 @Override public void processTerminated(int code) {
285 @Override public void startFailed(Throwable exception) {
286 startFailed.set(true);
287 errorOutput.add("Failed to start Git process");
288 errorOutput.add(ExceptionUtil.getThrowableText(exception));
292 handler.runInCurrentThread(null);
294 if (handler instanceof GitLineHandlerPasswordRequestAware && ((GitLineHandlerPasswordRequestAware)handler).hadAuthRequest()) {
295 errorOutput.add("Authentication failed");
298 final boolean success = !startFailed.get() && errorOutput.isEmpty() && (handler.isIgnoredErrorCode(exitCode.get()) || exitCode.get() == 0);
299 return new GitCommandResult(success, exitCode.get(), errorOutput, output);
303 * Check if the line looks line an error message
305 private static boolean isError(String text) {
306 for (String indicator : ERROR_INDICATORS) {
307 if (text.startsWith(indicator.toLowerCase())) {
314 // could be upper-cased, so should check case-insensitively
315 private static final String[] ERROR_INDICATORS = {
316 "error", "fatal", "Cannot apply", "Could not", "Interactive rebase already started", "refusing to pull", "cannot rebase:", "conflict"