Get project' git repositories in a project component

Hi,

I'm working on a plugin who need to detect if a git repository corresponding to a special remote is enabled. So I created my detetor, I created my Project Component (https://gist.github.com/Kendos-Kenlen/0c5ef1c75e31c1e856b6), but when I try to detect repositories, no repositories exist in the GitRepositoryManager (the following code print an empty array :

 
public CleverIdeaProjectComponent(Project project, GitRepositoryManager gitRepositoryManager, GitProjectDetector gitProjectDetector) {
  myProject = project;
  myGitRepositoryManager
= gitRepositoryManager;
  myGitProjectDetector
= gitProjectDetector;
  System
.out.println(gitRepositoryManager.getRepositories());
}


The problem doesn't happen if I start the detection with an action, so I suppose that the list of repositories is just not loaded. So, how can I resolve this issue ? My idea is to check git repositories at project starting to know if an application is linked with our service.

Another question : why some servcies are declared with :

<projectService serviceInterface="" serviceImplementation=""/>

and some other with :

 
<projectService serviceImplementation=""/>


Which one is better/correct and what's the difference if the Interface and the Implementation are the same ?

20 comments
Comment actions Permalink

I suppose that the list of repositories is just not loaded. So, how can I resolve this issue ? My idea is to check git repositories at project starting to know if an application is linked with our service.


You're right. If you look into the VcsRepositoryManager (the actual container of this information) you'll see that the repositories are updated after directoryMappingsChange, which at first happens when the ProjectLevelVcsManager's mappings are initialized.

In IDEA source code we use ProjectLevelVcsManagerImpl.addInitializationRequest to make sure our components are initialized after the mappings do (VcsInitObject#MAPPINGS). The VcsInitObject is a enum, so currently there is no way to pass your own request there, but you can try using one of the existing ones:

ProjectLevelVcsManagerImpl vcsManager; // get an instance of ProjectLevelVcsManager in the constructor of your component and cast to Impl
GitRepositoryManager gitRepoManager;
...
vcsManager.addInitializationRequest(VcsInitObject.AFTER_COMMON, new Runnable() {
  public void run() {
     VirtualFile[] gitRoots = vcsManager.getRootsUnderVcs(GitVcs.getInstance(project));
     for (VirtualFile root : gitRoots) {
       GitRepository repo = gitRepoManager.getRepositoryForRoot(root);
       if (repo != null) { // in general should be not null here, but not guaranteed since anything can happen with mappings in the meantime
         // go on with the checkings.
       }
       else { LOG.warn or whatever }
     }
  }
});



The GitRepositoryManager doesn't use this VcsInitObject technique for now (so it is not defined when it will be updated initially or after mappings change), but GitRepositoryManager#getRepositoryForRoot will cause the collection of repositories to be updated automatically if needed.

Which one is better/correct and what's the difference if the Interface and the Implementation are the same ?


There is no difference if the interface and implementation are the same: the second notation is just a shorthand.
0
Comment actions Permalink

Hi,

Thank you for your help, I successfuly implemented the project component.gges

I suggest to document the way IntelliJ use to load part like the VCS système (because I suppose that it's not the only thing who works like that), the event system and other things related.
I didn't find documentation about it in the SDK doc, but I think that it's useful for developpers. I already created an issue about VCS documentation here : https://youtrack.jetbrains.com/issue/IJSDK-61 but I think that VCS isn't the only part of the IDE covered by this loading process.

Edit : How can I run a Git Push ? I want to create a button to easily deploy the last commit on our service, but I don't really know what should I do. I found the "GitPushOperation" class but I'm not sure how to use it, especially for the 3rd parameter :

GitPushSupport gitPushSupport = ServiceManager.getService(e.getProject(), GitPushSupport.class);
GitPushOperation gitPushOperation = new GitPushOperation(e.getProject(), gitPushSupport, ???, null, false);
0
Comment actions Permalink

Unfortunately most of the functionality which you've mentioned (including most of Git support, and including the VcsInitObject stuff which we've discussed above - as you might have noticed, it is not API) has no official API, that's why it is not documented. However, we're working on this problem and plan both to extract API for most useful parts of the functionality, and to document it properly.

To push don't call GitPushOperation directly. Instead use GitPushSupport.getPusher.push(). For example, like this:

GitPushSupport pushSupport = ServiceManager.getService(e.getProject(), GitPushSupport.class);

GitPushSource source = pushSupport.getSource(repository); // this simply creates the GitPushSource wrapper around the current branch or current revision in case of the detached HEAD
GitPushTarget target = // create target either directly, or by using some methods from GitPushSupport. Just check them, most probably you'll find what's needed.
Map<GitRepository, PushSpec<GitPushSource, GitPushTarget> pushSpecs = Collections.singletonMap(repository, new PushSpec(source, target));
 
pushSupport.getPusher().push(specs, null, false);
 
Please check if that works for you.
0
Comment actions Permalink

Hi,

Okay, I understand now why there is not an "easy" way to play with Git and the VCS system. I suppose that providing an API will be a good thing for plugins like Settings Repository. I looked the source code for my plugin, and it use JGit instead of the IntelliJ git plugin, is not really "logic", since intelliJ provide the necessary to manage Git.

If I can make a comment about IntelliJ platform, is that I find it quite "messy". I spend a lot of time watching how IntelliJ words for my plugin, and because it interests me, and I must say that I do not find the answer easily.
IntelliJ is well built, it is not the concern (and it's definitly my prefered IDE since 2 years now :D). We can do a lot of things when creating a plugin and it is a very rich IDE and platform, but it is very poorly documented (a big part of the code lack of documentation). Only the Open API is documented so it take long hours to find how to do a simple thing that isn't documented in the SDK doc.
More documentation would be definitely a plus. I would like to contribute to the platform to add features that I would like to see implemented, or improve what I can, but it is currently almost impossible because it is really hard to navigate and it miss from a lot of documentation.

I tried some month ago to solve this issue (that I created) : https://youtrack.jetbrains.com/issue/CPP-1374 but I failed because of this lack of documentation. IntelliJ architecture (folder structures) is not necessarily obvious in the begriming and find what you want is hard (what is the difference between ***-api and ***-impl? Which class sould I use ? What is this folder for ?...). I created a very little pull request the other day (https://github.com/JetBrains/intellij-community/pull/293) because I was searching a function, and no JavaDoc was available so I had to go in the source code, etc ... bref, I lost time with a simple thing (while it takes two lines of doc) and it happen often, so I wrote two lines of doc to help people after me (and to use the Quick Doc :D ).

I sincerely hope you will document more code, it would please me a lot to be able to put my hands in the code, it's very interesting for me as a student, I learn a lot in Java. And two lines of code take only few seconds to be written, but help to save many hours of searching ! ;) So please, provide more JavaDoc and SDK Doc ! http://img11.hostingpics.net/pics/135152rszlesmileysansbouchereference2.jpg

________________________


Btw, I tried the code you gave me, but it can't works because GitPushSource is an abstract class and is not public, so I can't access it in my plugin.

0
Comment actions Permalink
it use JGit instead of the IntelliJ git plugin, is not really "logic", since intelliJ provide the necessary to manage Git.



Honestly, I have no idea why does it use JGit. I will ask the author. My assumption is: it is so to avoid Git installation requirement: there could be a lot of users of the Settings Repository which don't use Git at all.

what is the difference between ***-api and ***-impl? Which class sould I use ? What is this folder for ?...)



Well, this one is simple. You should use classes from "-api" modules, but not from "-impl". The api-modules form the API, which means that in general they provide more clean and understandable interfaces with javadocs and, which is even more important, which are changes with much more care to avoid breaking of 3-party plugins. On the other hand, the impl-modules can rapidly and heavily change, and nothing is guaranteed here.
Of course, sometimes you don't find the API you need for you task: in this case the right thing to do is to request the necessary API from the IntelliJ IDEA team, and use classes from impl on our own risk until that API is created.

I created a very little pull request the other day because I was searching a function, and no JavaDoc was available so I had to go in the source code, etc


Sending such a documention pull request is a very good move, let me say. Will be accepted shortly.

Btw, I tried the code you gave me, but it can't works because GitPushSource is an abstract class and is not public, so I can't access it in my plugin.


The problem is not that it is abstract, but because it is not public.
But the solution is simple: just use PushSource instead of GitPushSource:

 
PushSource source  = support.getSource(repository);


Alternatively, you may implement the PushSource on your own. Or (which is a dirty but common way to overcome the package-visibility problem), you can make a public wrapper (or descendant) of the GitPushSource and place it into the git4idea.push package. But better stick with the PushSource since you don't need to access GitPushSource directly anyway.

0
Comment actions Permalink

Hi,

it use JGit instead of the IntelliJ git plugin, is not really "logic", since intelliJ provide the necessary to manage Git.

Honestly, I have no idea why does it use JGit. I will ask the author. My assumption is: it is so to avoid Git installation requirement: there could be a lot of users of the Settings Repository which don't use Git at all.

I thought to this reason too, but considering that there is not an API available for Git (or others VCS), I suppose it was just more convenient. As you explained, it's not easy to play with Git for the moment so I suppose it was the easiest way.

what is the difference between ***-api and ***-impl? Which class sould I use ? What is this folder for ?...)

Well, this one is simple. You should use classes from "-api" modules, but not from "-impl". The api-modules form the API, which means that in general they provide more clean and understandable interfaces with javadocs and, which is even more important, which are changes with much more care to avoid breaking of 3-party plugins. On the other hand, the impl-modules can rapidly and heavily change, and nothing is guaranteed here.

Of course, sometimes you don't find the API you need for you task: in this case the right thing to do is to request the necessary API from the IntelliJ IDEA team, and use classes from impl on our own risk until that API is created.

Thank you for the explaination. it was an example, but it clarify what I thought. I didn't had problem with missing API except for git but if I need an API, I'll ask it on youtrack then ! And I follow the evolution of the platform with EAP so I can adjust my code if a change is made. EAP are cool for that !


I'll continue to document code if I find undocumented method that I can complete (and try to create bigger pull requests :p). This is the only thing that I do to help for the moment.

Btw, I tried the code you gave me, but it can't works because GitPushSource is an abstract class and is not public, so I can't access it in my plugin.


The problem is not that it is abstract, but because it is not public.
But the solution is simple: just use PushSource instead of GitPushSource:

PushSource source  = support.getSource(repository);
 

Alternatively, you may implement the PushSource on your own. Or (which is a dirty but common way to overcome the package-visibility problem), you can make a public wrapper (or descendant) of the GitPushSource and place it into the git4idea.push package. But better stick with the PushSource since you don't need to access GitPushSource directly anyway.

I tried to use PushSource, but pushSupport.getPusher().push() is problematic : it take as first parameter a Map<GitRepository, PushSpec<GitPushSource, GitPushTarget>>, who use GitPushSource explicitly... I tried to crate a public wrapper named GitPushSourcePub, but the push method doesn't accept it ... :(
I'm sorry if I miss a trick, I'm far from being a Java expert.

The only way I found it's to put my class directly in the package (to have git4idea.push.DeployAction) But it's really disgusting and I really appreciate to do things differently ...

0
Comment actions Permalink

I tried to use PushSource, but pushSupport.getPusher().push() is problematic : it take as first parameter a Map<GitRepository, PushSpec<GitPushSource, GitPushTarget>>, who use GitPushSource explicitly... I tried to crate a public wrapper named GitPushSourcePub, but the push method doesn't accept it ... :(


Ah, I've missed this restriction.

OK, I've just made GitPushSource public, and pushed the change to the master branch on intellij-community. So you're free to use it now.

0
Comment actions Permalink

Hi,

I need some help to build my plugin, I can't find a good way to do what I want...

My plugin is for a service similar to Heroku. To help the developer, we decided to offer him the possibility to directly push on our service from the IDE, get logs, create new app, ...
I've created a detector that read all remotes in the project and, if I detect a remote corresponding to an application on our service, I request our API to check if the user own the application and to get info about it.

But I've a problem : to push, I need to know where to push (logic) but I don't know how I can link one of my "Applications" (who is a simple object) to the good repository ?

For example, I can have the following git configuration :

[remote "origin"]      url = git@bitbucket.org:GauthierP/novalis.git      fetch = +refs/heads/*:refs/remotes/origin/* [branch "master"]      remote = origin      merge = refs/heads/master [remote "clever"]      url = git+ssh://git@push.par.clever-cloud.com/app_[ID].git      fetch = +refs/heads/*:refs/remotes/clever/*


How, when I want to push, can I specity that I want to push on the clever remote, and how can I associate the "Application" to its remote and persist it ? I looked, but it's not possible to get the repository from a remote (ex remote.getRepository()).
I tried to persist a GitRepository but it's seriously a bad idea (it just doesn't work).

0
Comment actions Permalink

Why don't you just use GitRemote object? Or a pair of GitRepository and GitRemote.

0
Comment actions Permalink

When I try to persist a GitRepository or a GitRemote, it gives me that :

 
<option name="repository">
  <GitRepository />
</option>
<option name="remote">
  <GitRemote />
</option>


and when I reopen the project, I've a deserialization error.

Another question, how can i specity the remote to push ? I looked, and by default, when I do "GitPushTarget target = pushSupport.getDefaultTarget(repository);" (or when I create a GitTarget), I can't choose it.

0
Comment actions Permalink

That's because these are custom objects that are not annotated as persistable. See com.intellij.dvcs.push.PushSettings.ForcePushTargetInfo for example of a custom persistable class.

However, in this case I'd just store 2 Strings: the path to the root + the URL of the remote (or wrap them into a custom class). Should be enough to get proper GitRepository and GitRemote objects when you need them (GitRepositoryManager.getRepositoryForRoot(), GitRepository.getRemotes() -> find by URL).

Another question, how can i specity the remote to push ? I looked, and by default, when I do "GitPushTarget target = pushSupport.getDefaultTarget(repository);" (or when I create a GitTarget), I can't choose it.


Just create a new GitPushTarget from a GitRemoteBranch, which you can either get from the list of known remote branches, either create a new one. That's exactly what is being done in GitPushSupport#makeTargetForNewBranch.

0
Comment actions Permalink

Okay, I understand. Thank you for your help !

0
Comment actions Permalink

I tried to get the GitPushSupport, but

GitPushSupport pushSupport = ServiceManager.getService(project, GitPushSupport.class);

is always null... I looked the GIt plugin but it never get from the ServiceManager.
0
Comment actions Permalink

I was wrong. The GitPushSupport is not a project service, but an implementation of the common extension point. Currently the only way to get an instance of the GitPushSupport is to traverse over all existing extension point implementations and find the one which corresponds to the VCS you have.

I've just pushed a change that adds a utility method for this task to the DvcsUtil class: com.intellij.dvcs.DvcsUtil#getPushSupport

0
Comment actions Permalink

After some works on another project, I'm back to this one with a problem.
When I launch a push operation :

private void pushOnClever(@NotNull DeployDialog dialog, @NotNull Project project) {
  Application application = dialog.getSelectedItem();
  VirtualFile gitRoot = LocalFileSystem.getInstance().findFileByIoFile(new File(application.deployment.repository));
  assert gitRoot != null;
  GitRepositoryManager repositoryManager = ServiceManager.getService(project, GitRepositoryManager.class);
  GitRepository repository = repositoryManager.getRepositoryForRoot(gitRoot);
  if (repository == null) return;

  GitRemote remote = getRemote(repository.getRemotes(), application.deployment.url);
  if (remote == null) return;
  GitRemoteBranch branch = getBranch(repository, remote);

  ProjectLevelVcsManager projectLevelVcsManager = ProjectLevelVcsManager.getInstance(project);
  AbstractVcs abstractVcs = projectLevelVcsManager.getVcsFor(gitRoot);
  assert abstractVcs != null;
  GitPushSupport pushSupport = (GitPushSupport)DvcsUtil.getPushSupport(abstractVcs);
  assert pushSupport != null;
  GitPushSource source = pushSupport.getSource(repository);
  GitPushTarget target = new GitPushTarget(branch, false);

  PushSpec<GitPushSource, GitPushTarget> pushSourceGitPushTargetPushSpec = new PushSpec<>(source, target);
  Map<GitRepository, PushSpec<GitPushSource, GitPushTarget>> pushSpecs =
    Collections.singletonMap(repository, pushSourceGitPushTargetPushSpec);

  pushSupport.getPusher().push(pushSpecs, null, false);
}

private GitRemote getRemote(@NotNull Collection<GitRemote> gitRemoteCollections, String remoteUrl) {
  for (GitRemote gitRemote : gitRemoteCollections) {
    int remoteIndex = gitRemote.getUrls().indexOf(remoteUrl);
    if (remoteIndex != -1) {
      return gitRemote;
    }
  }
  return null;
}

@NotNull
private GitRemoteBranch getBranch(@NotNull GitRepository repository, @NotNull GitRemote remote) {
  for (GitRemoteBranch remoteBranch : repository.getBranches().getRemoteBranches()) {
    if (remoteBranch.getRemote().equals(remote) && remoteBranch.getName().equals("master")) return remoteBranch;
  }
  return new GitStandardRemoteBranch(remote, "master", GitBranch.DUMMY_HASH);
}


... the process block while executing "pushSupport.getPusher().push()". I've looked with the debugger and found that it block here (thread).
I didn't go deeper in IntelliJ with the debugger (I tried but ... it goes too deep). The IDE freeze and I can't continue to use it, and the push operation isn't performed.

0
Comment actions Permalink

That's obviously because you're running Git from the EDT thread, thus freezing the whole UI until the operation completes. Which can take forever if Git waits for a password, and the password prompt can't be displayed - well, because the EDT is occupied.

The solution is to make the task in another thread. Use one of 3 approaches:
* ApplicationManager.getApplication().executeOnPooledThread <- to execute the task on a background thread.
* ProgressManager.getInstance().run(new Task.Backgroundable()) <- to execute the task on a background thread with a progress indicator in the status bar (most of Git commands in IDEA use this approach).
* ProgressManager.getInstance().run(new Task.Modal()) <- to execute the task with a modal progress dialog. This will not allow UI interaction (because the progress is modal), but UI woudn't freeze, and will show the password prompt. This approach is rarely needed when executing something like git push, so I'm just mentioning it to make you have the full picture.

0
Comment actions Permalink

\o/
Thank you ! I didn't thought to that. Everything work well now ! :)

0
Comment actions Permalink

Hi,

I want to create a "Check out from VC" action for my plugin, similar to the Github one. I looked in the plugin code but I didn't found what is used to perform the action (add an item to the "Check out from VC" menu, perform the cloning and open the project). Can you help me ?

And I saw that GitBranch.DUMMY_HASH is marked as deprecated. What should I use instead ?

0
Comment actions Permalink

You have to implement the `checkoutProvider` extension point: implement the CheckoutProvider interface and add the entry to your plugin.xml. Since you're going to checkout from Git, you'll probably reuse some stuff from GitCheckoutProvider and GithubCheckoutProvider (which obviously share some methods between each other).

If you know the commit which the branch is pointing to - use its hash. Otherwise you can use DUMMY_HASH. The hash is retrived from GitBranch only in few specific situations (search for GitBranch#getHash() usages), so it is safe to use arbitrary value in others.

We've marked DUMMY_HASH as deprecated, but later found out that it is not always convenient to identify the hash each time the GitBranch object is needed, so some day we'll probably remove this parameter from GitBranch at all.

0

Please sign in to leave a comment.