@@ -2637,44 +2637,45 @@ public class GitBlit implements ServletContextListener { | |||
* | |||
* @param repository | |||
* @param user | |||
* @return true, if successful | |||
* @return the repository model of the fork, if successful | |||
* @throws GitBlitException | |||
*/ | |||
public boolean fork(RepositoryModel repository, UserModel user) { | |||
public RepositoryModel fork(RepositoryModel repository, UserModel user) throws GitBlitException { | |||
String cloneName = MessageFormat.format("~{0}/{1}.git", user.username, StringUtils.stripDotGit(StringUtils.getLastPathElement(repository.name))); | |||
String fromUrl = MessageFormat.format("file://{0}/{1}", repositoriesFolder.getAbsolutePath(), repository.name); | |||
// clone the repository | |||
try { | |||
// clone the repository | |||
JGitUtils.cloneRepository(repositoriesFolder, cloneName, fromUrl, true, null); | |||
// create a Gitblit repository model for the clone | |||
RepositoryModel cloneModel = repository.cloneAs(cloneName); | |||
cloneModel.owner = user.username; | |||
updateRepositoryModel(cloneName, cloneModel, false); | |||
if (AuthorizationControl.NAMED.equals(cloneModel.authorizationControl)) { | |||
// add the owner of the source repository to the clone's access list | |||
if (!StringUtils.isEmpty(repository.owner)) { | |||
UserModel owner = getUserModel(repository.owner); | |||
if (owner != null) { | |||
owner.repositories.add(cloneName); | |||
updateUserModel(owner.username, owner, false); | |||
} | |||
} catch (Exception e) { | |||
throw new GitBlitException(e); | |||
} | |||
// create a Gitblit repository model for the clone | |||
RepositoryModel cloneModel = repository.cloneAs(cloneName); | |||
cloneModel.owner = user.username; | |||
updateRepositoryModel(cloneName, cloneModel, false); | |||
if (AuthorizationControl.NAMED.equals(cloneModel.authorizationControl)) { | |||
// add the owner of the source repository to the clone's access list | |||
if (!StringUtils.isEmpty(repository.owner)) { | |||
UserModel owner = getUserModel(repository.owner); | |||
if (owner != null) { | |||
owner.repositories.add(cloneName); | |||
updateUserModel(owner.username, owner, false); | |||
} | |||
// inherit origin's access lists | |||
List<String> users = getRepositoryUsers(repository); | |||
setRepositoryUsers(cloneModel, users); | |||
List<String> teams = getRepositoryTeams(repository); | |||
setRepositoryTeams(cloneModel, teams); | |||
} | |||
// add this clone to the cached model | |||
addToCachedRepositoryList(cloneModel.name, cloneModel); | |||
return true; | |||
} catch (Exception e) { | |||
logger.error("failed to fork", e); | |||
// inherit origin's access lists | |||
List<String> users = getRepositoryUsers(repository); | |||
setRepositoryUsers(cloneModel, users); | |||
List<String> teams = getRepositoryTeams(repository); | |||
setRepositoryTeams(cloneModel, teams); | |||
} | |||
return false; | |||
// add this clone to the cached model | |||
addToCachedRepositoryList(cloneModel.name, cloneModel); | |||
return cloneModel; | |||
} | |||
} |
@@ -145,7 +145,10 @@ public class RepositoryModel implements Serializable, Comparable<RepositoryModel | |||
public RepositoryModel cloneAs(String cloneName) { | |||
RepositoryModel clone = new RepositoryModel(); | |||
clone.originRepository = name; | |||
clone.name = cloneName; | |||
clone.projectPath = StringUtils.getFirstPathElement(cloneName); | |||
clone.isBare = true; | |||
clone.description = description; | |||
clone.accessRestriction = accessRestriction; | |||
clone.authorizationControl = authorizationControl; |
@@ -34,6 +34,7 @@ import com.gitblit.wicket.pages.CommitDiffPage; | |||
import com.gitblit.wicket.pages.CommitPage; | |||
import com.gitblit.wicket.pages.DocsPage; | |||
import com.gitblit.wicket.pages.FederationRegistrationPage; | |||
import com.gitblit.wicket.pages.ForkPage; | |||
import com.gitblit.wicket.pages.ForksPage; | |||
import com.gitblit.wicket.pages.GitSearchPage; | |||
import com.gitblit.wicket.pages.GravatarProfilePage; | |||
@@ -120,6 +121,7 @@ public class GitBlitWebApp extends WebApplication { | |||
mount("/projects", ProjectsPage.class); | |||
mount("/user", UserPage.class, "user"); | |||
mount("/forks", ForksPage.class, "r"); | |||
mount("/fork", ForkPage.class, "r"); | |||
} | |||
private void mount(String location, Class<? extends WebPage> clazz, String... parameters) { |
@@ -323,7 +323,7 @@ gb.fork = fork | |||
gb.forks = forks | |||
gb.forkRepository = fork {0}? | |||
gb.repositoryForked = {0} has been forked | |||
gb.repositoryForkFailed= failed to fork {1} | |||
gb.repositoryForkFailed= fork has failed | |||
gb.personalRepositories = personal repositories | |||
gb.allowForks = allow forks | |||
gb.allowForksDescription = allow authorized users to fork this repository | |||
@@ -333,4 +333,7 @@ gb.canForkDescription = user is permitted to fork authorized repositories | |||
gb.myFork = view my fork | |||
gb.forksProhibited = forks prohibited | |||
gb.forksProhibitedWarning = this repository forbids forks | |||
gb.noForks = {0} has no forks | |||
gb.noForks = {0} has no forks | |||
gb.forkNotAuthorized = sorry, you are not authorized to fork {0} | |||
gb.forkInProgress = fork in progress | |||
gb.preparingFork = Gitblit is preparing your fork |
@@ -17,6 +17,7 @@ package com.gitblit.wicket; | |||
import java.util.Map; | |||
import java.util.TimeZone; | |||
import java.util.concurrent.atomic.AtomicBoolean; | |||
import org.apache.wicket.Page; | |||
import org.apache.wicket.PageParameters; | |||
@@ -42,8 +43,11 @@ public final class GitBlitWebSession extends WebSession { | |||
private String requestUrl; | |||
private AtomicBoolean isForking; | |||
public GitBlitWebSession(Request request) { | |||
super(request); | |||
isForking = new AtomicBoolean(); | |||
} | |||
public void invalidate() { | |||
@@ -134,6 +138,14 @@ public final class GitBlitWebSession extends WebSession { | |||
errorMessage = null; | |||
return msg; | |||
} | |||
public boolean isForking() { | |||
return isForking.get(); | |||
} | |||
public void isForking(boolean val) { | |||
isForking.set(val); | |||
} | |||
public static GitBlitWebSession get() { | |||
return (GitBlitWebSession) Session.get(); |
@@ -0,0 +1,40 @@ | |||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> | |||
<html xmlns="http://www.w3.org/1999/xhtml" | |||
xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd" | |||
xml:lang="en" | |||
lang="en"> | |||
<body> | |||
<wicket:head> | |||
<noscript> | |||
<meta http-equiv="refresh" content="5"></meta> | |||
</noscript> | |||
<script type="text/javascript""> | |||
function doLoad() { setTimeout( "refresh()", 5*1000 ); } | |||
function refresh() { window.location.reload(); } | |||
</script> | |||
</wicket:head> | |||
<wicket:extend> | |||
<!-- need to specify body.onload --> | |||
<body onload="doLoad()"> | |||
<div class="row"> | |||
<div class="span6 offset3"> | |||
<div wicket:id="forkText" class="pageTitle project" style="border:0;font-weight:bold; text-align:center;">[fork text]</div> | |||
</div> | |||
<div class="span4 offset4"> | |||
<div class="progress progress-striped active"> | |||
<div class="bar" style="width: 100%;"></div> | |||
</div> | |||
</div> | |||
<div class="span6 offset3"> | |||
<div style="opacity:0.2;"> | |||
<center><img style="padding:10px" src="git-black.png"></img></center> | |||
</div> | |||
</div> | |||
</div> | |||
</body> | |||
</wicket:extend> | |||
</body> | |||
</html> |
@@ -0,0 +1,107 @@ | |||
/* | |||
* Copyright 2012 gitblit.com. | |||
* | |||
* 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 com.gitblit.wicket.pages; | |||
import java.text.MessageFormat; | |||
import org.apache.wicket.PageParameters; | |||
import org.apache.wicket.markup.html.basic.Label; | |||
import org.slf4j.LoggerFactory; | |||
import com.gitblit.GitBlit; | |||
import com.gitblit.models.RepositoryModel; | |||
import com.gitblit.models.UserModel; | |||
import com.gitblit.wicket.GitBlitWebSession; | |||
import com.gitblit.wicket.GitblitRedirectException; | |||
import com.gitblit.wicket.WicketUtils; | |||
public class ForkPage extends RepositoryPage { | |||
public ForkPage(PageParameters params) { | |||
super(params); | |||
setVersioned(false); | |||
GitBlitWebSession session = GitBlitWebSession.get(); | |||
RepositoryModel repository = getRepositoryModel(); | |||
UserModel user = session.getUser(); | |||
boolean canFork = user.canForkRepository(repository); | |||
if (!canFork) { | |||
// redirect to the summary page if this repository is not empty | |||
GitBlitWebSession.get().cacheErrorMessage( | |||
MessageFormat.format(getString("gb.forkNotAuthorized"), repository.name)); | |||
throw new GitblitRedirectException(SummaryPage.class, WicketUtils.newRepositoryParameter(repository.name)); | |||
} | |||
String fork = GitBlit.self().getFork(user.username, repository.name); | |||
if (fork != null) { | |||
// redirect to user's fork | |||
throw new GitblitRedirectException(SummaryPage.class, WicketUtils.newRepositoryParameter(fork)); | |||
} | |||
add(new Label("forkText", getString("gb.preparingFork"))); | |||
if (!session.isForking()) { | |||
// prepare session | |||
session.isForking(true); | |||
// fork it | |||
ForkThread forker = new ForkThread(repository, session); | |||
forker.start(); | |||
} | |||
} | |||
@Override | |||
protected boolean allowForkControls() { | |||
return false; | |||
} | |||
@Override | |||
protected String getPageName() { | |||
return "fork"; | |||
} | |||
/** | |||
* ForkThread does the work of working the repository in a background | |||
* thread. The completion status is tracked through a session variable and | |||
* monitored by this page. | |||
*/ | |||
private static class ForkThread extends Thread { | |||
private final RepositoryModel repository; | |||
private final GitBlitWebSession session; | |||
public ForkThread(RepositoryModel repository, GitBlitWebSession session) { | |||
this.repository = repository; | |||
this.session = session; | |||
} | |||
@Override | |||
public void run() { | |||
UserModel user = session.getUser(); | |||
try { | |||
GitBlit.self().fork(repository, user); | |||
} catch (Exception e) { | |||
LoggerFactory.getLogger(ForkPage.class).error(MessageFormat.format("Failed to fork {0} for {1}", repository.name, user.username), e); | |||
} finally { | |||
session.isForking(false); | |||
} | |||
} | |||
} | |||
} |
@@ -28,12 +28,10 @@ import java.util.Set; | |||
import org.apache.wicket.Component; | |||
import org.apache.wicket.PageParameters; | |||
import org.apache.wicket.RedirectException; | |||
import org.apache.wicket.markup.html.basic.Label; | |||
import org.apache.wicket.markup.html.form.DropDownChoice; | |||
import org.apache.wicket.markup.html.form.TextField; | |||
import org.apache.wicket.markup.html.link.ExternalLink; | |||
import org.apache.wicket.markup.html.link.Link; | |||
import org.apache.wicket.markup.html.panel.Fragment; | |||
import org.apache.wicket.model.IModel; | |||
import org.apache.wicket.model.Model; | |||
@@ -62,7 +60,6 @@ import com.gitblit.wicket.PageRegistration; | |||
import com.gitblit.wicket.PageRegistration.OtherPageLink; | |||
import com.gitblit.wicket.SessionlessForm; | |||
import com.gitblit.wicket.WicketUtils; | |||
import com.gitblit.wicket.panels.BasePanel.JavascriptEventConfirmation; | |||
import com.gitblit.wicket.panels.LinkPanel; | |||
import com.gitblit.wicket.panels.NavigationPanel; | |||
import com.gitblit.wicket.panels.RefsPanel; | |||
@@ -171,6 +168,10 @@ public abstract class RepositoryPage extends BasePage { | |||
} | |||
return pages; | |||
} | |||
protected boolean allowForkControls() { | |||
return true; | |||
} | |||
@Override | |||
protected void setupPage(String repositoryName, String pageName) { | |||
@@ -230,7 +231,7 @@ public abstract class RepositoryPage extends BasePage { | |||
} | |||
// fork controls | |||
if (user == null) { | |||
if (!allowForkControls() || user == null) { | |||
// must be logged-in to fork, hide all fork controls | |||
add(new ExternalLink("forkLink", "").setVisible(false)); | |||
add(new ExternalLink("myForkLink", "").setVisible(false)); | |||
@@ -268,25 +269,8 @@ public abstract class RepositoryPage extends BasePage { | |||
// can fork and we do not have one | |||
add(new Label("forksProhibitedIndicator").setVisible(false)); | |||
add(new ExternalLink("myForkLink", "").setVisible(false)); | |||
Link<Void> forkLink = new Link<Void>("forkLink") { | |||
private static final long serialVersionUID = 1L; | |||
@Override | |||
public void onClick() { | |||
UserModel user = GitBlitWebSession.get().getUser(); | |||
RepositoryModel model = getRepositoryModel(); | |||
String asFork = MessageFormat.format("~{0}/{1}.git", user.username, StringUtils.stripDotGit(StringUtils.getLastPathElement(model.name))); | |||
if (GitBlit.self().fork(model, GitBlitWebSession.get().getUser())) { | |||
throw new RedirectException(SummaryPage.class, WicketUtils.newRepositoryParameter(asFork)); | |||
} else { | |||
error(MessageFormat.format(getString("gb.repositoryForkFailed"), model)); | |||
} | |||
} | |||
}; | |||
forkLink.add(new JavascriptEventConfirmation("onclick", MessageFormat.format( | |||
getString("gb.forkRepository"), getRepositoryModel()))); | |||
add(forkLink); | |||
String url = getRequestCycle().urlFor(ForkPage.class, WicketUtils.newRepositoryParameter(model.name)).toString(); | |||
add(new ExternalLink("forkLink", url)); | |||
} | |||
} | |||
@@ -22,7 +22,6 @@ import java.util.Map; | |||
import org.apache.wicket.Component; | |||
import org.apache.wicket.Localizer; | |||
import org.apache.wicket.Page; | |||
import org.apache.wicket.PageParameters; | |||
import org.apache.wicket.markup.html.basic.Label; | |||
import org.apache.wicket.markup.html.link.BookmarkablePageLink; | |||
@@ -153,7 +152,6 @@ public class ProjectRepositoryPanel extends BasePanel { | |||
@Override | |||
public void onClick() { | |||
if (GitBlit.self().deleteRepositoryModel(entry)) { | |||
info(MessageFormat.format(getString("gb.repositoryDeleted"), entry)); | |||
// redirect to the owning page | |||
if (entry.isPersonalRepository()) { | |||
setResponsePage(getPage().getClass(), WicketUtils.newUsernameParameter(entry.projectPath.substring(1))); |
@@ -305,11 +305,11 @@ public class RepositoriesPanel extends BasePanel { | |||
@Override | |||
public void onClick() { | |||
if (GitBlit.self().deleteRepositoryModel(entry)) { | |||
info(MessageFormat.format(getString("gb.repositoryDeleted"), entry)); | |||
if (dp instanceof SortableRepositoriesProvider) { | |||
info(MessageFormat.format(getString("gb.repositoryDeleted"), entry)); | |||
((SortableRepositoriesProvider) dp).remove(entry); | |||
} else { | |||
((RepositoriesProvider) dp).remove(entry); | |||
setResponsePage(getPage().getClass(), getPage().getPageParameters()); | |||
} | |||
} else { | |||
error(MessageFormat.format(getString("gb.repositoryDeleteFailed"), entry)); |