diff options
author | James Moger <james.moger@gitblit.com> | 2014-03-06 12:49:02 -0600 |
---|---|---|
committer | James Moger <james.moger@gitblit.com> | 2014-03-06 12:49:02 -0600 |
commit | aae58435191c1b4e73ef7c5447e7a0832c7f0e53 (patch) | |
tree | 5ddf9d5be1cc9f0197148be99576214eb92a081b | |
parent | e6ea45327e387729e200ded58b91c4e21936d647 (diff) | |
parent | 148b40840657260d103eda1897d61f2d949656de (diff) | |
download | gitblit-aae58435191c1b4e73ef7c5447e7a0832c7f0e53.tar.gz gitblit-aae58435191c1b4e73ef7c5447e7a0832c7f0e53.zip |
Merged #22 "Tie mirroring, pushing, and the BranchTicketService together"
-rw-r--r-- | build.xml | 4 | ||||
-rw-r--r-- | src/main/distrib/linux/reindex-tickets.sh | 9 | ||||
-rw-r--r-- | src/main/distrib/win/reindex-tickets.cmd | 17 | ||||
-rw-r--r-- | src/main/java/com/gitblit/Constants.java | 2 | ||||
-rw-r--r-- | src/main/java/com/gitblit/git/GitblitReceivePack.java | 9 | ||||
-rw-r--r-- | src/main/java/com/gitblit/git/PatchsetReceivePack.java | 33 | ||||
-rw-r--r-- | src/main/java/com/gitblit/git/ReceiveCommandEvent.java | 38 | ||||
-rw-r--r-- | src/main/java/com/gitblit/service/MirrorService.java | 32 | ||||
-rw-r--r-- | src/main/java/com/gitblit/servlet/RpcServlet.java | 15 | ||||
-rw-r--r-- | src/main/java/com/gitblit/tickets/BranchTicketService.java | 71 | ||||
-rw-r--r-- | src/main/java/com/gitblit/tickets/ITicketService.java | 4 | ||||
-rw-r--r-- | src/main/java/com/gitblit/utils/RpcUtils.java | 31 | ||||
-rw-r--r-- | src/site/rpc.mkd | 2 | ||||
-rw-r--r-- | src/site/tickets_replication.mkd | 135 |
14 files changed, 387 insertions, 15 deletions
@@ -570,7 +570,8 @@ <page name="overview" src="tickets_overview.mkd" /> <page name="using" src="tickets_using.mkd" /> <page name="barnum" src="tickets_barnum.mkd" /> - <page name="setup" src="tickets_setup.mkd" /> + <page name="setup" src="tickets_setup.mkd" />
+ <page name="replication & advanced administration" src="tickets_replication.mkd" /> </menu> <divider /> <page name="federation" src="federation.mkd" />
@@ -909,6 +910,7 @@ <page name="using" src="tickets_using.mkd" /> <page name="barnum" src="tickets_barnum.mkd" /> <page name="setup" src="tickets_setup.mkd" /> + <page name="replication & advanced administration" src="tickets_replication.mkd" />
</menu> <divider /> <page name="federation" src="federation.mkd" />
diff --git a/src/main/distrib/linux/reindex-tickets.sh b/src/main/distrib/linux/reindex-tickets.sh index 15939291..5a4fc34f 100644 --- a/src/main/distrib/linux/reindex-tickets.sh +++ b/src/main/distrib/linux/reindex-tickets.sh @@ -11,5 +11,14 @@ # # -------------------------------------------------------------------------- +if [ -z $1 ]; then + echo "Please specify your baseFolder!"; + echo ""; + echo "usage:"; + echo " reindex-tickets <baseFolder>"; + echo ""; + exit 1; +fi + java -cp gitblit.jar:./ext/* com.gitblit.ReindexTickets --baseFolder $1 diff --git a/src/main/distrib/win/reindex-tickets.cmd b/src/main/distrib/win/reindex-tickets.cmd index e28f45f5..c9116ca2 100644 --- a/src/main/distrib/win/reindex-tickets.cmd +++ b/src/main/distrib/win/reindex-tickets.cmd @@ -4,10 +4,19 @@ @REM Since the Tickets feature is undergoing massive churn it may be necessary
@REM to reindex tickets due to model or index changes.
@REM
-@REM Always use forward-slashes for the path separator in your parameters!!
+@REM usage:
+@REM reindex-tickets <baseFolder>
@REM
-@REM Set FOLDER to the baseFolder.
@REM --------------------------------------------------------------------------
-@SET FOLDER=data
+@if [%1]==[] goto nobasefolder
-@java -cp gitblit.jar;"%CD%\ext\*" com.gitblit.ReindexTickets --baseFolder %FOLDER%
+@java -cp gitblit.jar;"%CD%\ext\*" com.gitblit.ReindexTickets --baseFolder %1
+@goto end
+
+:nobasefolder
+@echo "Please specify your baseFolder!"
+@echo
+@echo " reindex-tickets c:/gitblit-data"
+@echo
+
+:end
\ No newline at end of file diff --git a/src/main/java/com/gitblit/Constants.java b/src/main/java/com/gitblit/Constants.java index 5b71eeb9..e93f7b1d 100644 --- a/src/main/java/com/gitblit/Constants.java +++ b/src/main/java/com/gitblit/Constants.java @@ -350,7 +350,7 @@ public class Constants { public static enum RpcRequest {
// Order is important here. anything above LIST_SETTINGS requires
// administrator privileges and web.allowRpcManagement.
- CLEAR_REPOSITORY_CACHE, GET_PROTOCOL, LIST_REPOSITORIES, LIST_BRANCHES, GET_USER, LIST_SETTINGS,
+ CLEAR_REPOSITORY_CACHE, REINDEX_TICKETS, GET_PROTOCOL, LIST_REPOSITORIES, LIST_BRANCHES, GET_USER, LIST_SETTINGS,
CREATE_REPOSITORY, EDIT_REPOSITORY, DELETE_REPOSITORY,
LIST_USERS, CREATE_USER, EDIT_USER, DELETE_USER,
LIST_TEAMS, CREATE_TEAM, EDIT_TEAM, DELETE_TEAM,
diff --git a/src/main/java/com/gitblit/git/GitblitReceivePack.java b/src/main/java/com/gitblit/git/GitblitReceivePack.java index 3a0eff22..e3e2faeb 100644 --- a/src/main/java/com/gitblit/git/GitblitReceivePack.java +++ b/src/main/java/com/gitblit/git/GitblitReceivePack.java @@ -344,6 +344,15 @@ public class GitblitReceivePack extends ReceivePack implements PreReceiveHook, P LOGGER.error(MessageFormat.format("Failed to update {0} pushlog", repository.name), e);
}
+ // check for updates pushed to the BranchTicketService branch
+ // if the BranchTicketService is active it will reindex, as appropriate
+ for (ReceiveCommand cmd : commands) {
+ if (Result.OK.equals(cmd.getResult())
+ && BranchTicketService.BRANCH.equals(cmd.getRefName())) {
+ rp.getRepository().fireEvent(new ReceiveCommandEvent(repository, cmd));
+ }
+ }
+
// run Groovy hook scripts
Set<String> scripts = new LinkedHashSet<String>();
scripts.addAll(gitblit.getPostReceiveScriptsInherited(repository));
diff --git a/src/main/java/com/gitblit/git/PatchsetReceivePack.java b/src/main/java/com/gitblit/git/PatchsetReceivePack.java index ae429d2e..c0ab8aeb 100644 --- a/src/main/java/com/gitblit/git/PatchsetReceivePack.java +++ b/src/main/java/com/gitblit/git/PatchsetReceivePack.java @@ -60,6 +60,7 @@ import com.gitblit.models.TicketModel.Patchset; import com.gitblit.models.TicketModel.PatchsetType;
import com.gitblit.models.TicketModel.Status;
import com.gitblit.models.UserModel;
+import com.gitblit.tickets.BranchTicketService;
import com.gitblit.tickets.ITicketService;
import com.gitblit.tickets.TicketMilestone;
import com.gitblit.tickets.TicketNotifier;
@@ -105,7 +106,7 @@ public class PatchsetReceivePack extends GitblitReceivePack { protected final TicketNotifier ticketNotifier;
- private boolean requireCleanMerge;
+ private boolean requireMergeablePatchset;
public PatchsetReceivePack(IGitblit gitblit, Repository db, RepositoryModel repository, UserModel user) {
super(gitblit, db, repository, user);
@@ -257,12 +258,26 @@ public class PatchsetReceivePack extends GitblitReceivePack { /** Execute commands to update references. */
@Override
protected void executeCommands() {
+ // we process patchsets unless the user is pushing something special
+ boolean processPatchsets = true;
+ for (ReceiveCommand cmd : filterCommands(Result.NOT_ATTEMPTED)) {
+ if (ticketService instanceof BranchTicketService
+ && BranchTicketService.BRANCH.equals(cmd.getRefName())) {
+ // the user is pushing an update to the BranchTicketService data
+ processPatchsets = false;
+ }
+ }
+
// workaround for JGit's awful scoping choices
//
// reset the patchset refs to NOT_ATTEMPTED (see validateCommands)
for (ReceiveCommand cmd : filterCommands(Result.OK)) {
if (isPatchsetRef(cmd.getRefName())) {
cmd.setResult(Result.NOT_ATTEMPTED);
+ } else if (ticketService instanceof BranchTicketService
+ && BranchTicketService.BRANCH.equals(cmd.getRefName())) {
+ // the user is pushing an update to the BranchTicketService data
+ processPatchsets = false;
}
}
@@ -292,7 +307,7 @@ public class PatchsetReceivePack extends GitblitReceivePack { continue;
}
- if (isPatchsetRef(cmd.getRefName())) {
+ if (isPatchsetRef(cmd.getRefName()) && processPatchsets) {
if (ticketService == null) {
sendRejection(cmd, "Sorry, the ticket service is unavailable and can not accept patchsets at this time.");
continue;
@@ -393,6 +408,8 @@ public class PatchsetReceivePack extends GitblitReceivePack { for (ReceiveCommand cmd : toApply) {
if (cmd.getResult() == Result.NOT_ATTEMPTED) {
sendRejection(cmd, "lock error: {0}", err.getMessage());
+ LOGGER.error(MessageFormat.format("failed to lock {0}:{1}",
+ repository.name, cmd.getRefName()), err);
}
}
}
@@ -436,10 +453,12 @@ public class PatchsetReceivePack extends GitblitReceivePack { case CREATE:
case UPDATE:
case UPDATE_NONFASTFORWARD:
- Collection<TicketModel> tickets = processMergedTickets(cmd);
- ticketsProcessed += tickets.size();
- for (TicketModel ticket : tickets) {
- ticketNotifier.queueMailing(ticket);
+ if (cmd.getRefName().startsWith(Constants.R_HEADS)) {
+ Collection<TicketModel> tickets = processMergedTickets(cmd);
+ ticketsProcessed += tickets.size();
+ for (TicketModel ticket : tickets) {
+ ticketNotifier.queueMailing(ticket);
+ }
}
break;
default:
@@ -537,7 +556,7 @@ public class PatchsetReceivePack extends GitblitReceivePack { case MERGEABLE:
break;
default:
- if (ticket == null || requireCleanMerge) {
+ if (ticket == null || requireMergeablePatchset) {
sendError("");
sendError("Your patchset can not be cleanly merged into {0}.", forBranch);
sendError("Please rebase your patchset and push again.");
diff --git a/src/main/java/com/gitblit/git/ReceiveCommandEvent.java b/src/main/java/com/gitblit/git/ReceiveCommandEvent.java new file mode 100644 index 00000000..84dabb35 --- /dev/null +++ b/src/main/java/com/gitblit/git/ReceiveCommandEvent.java @@ -0,0 +1,38 @@ +/* + * Copyright 2014 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.git; + +import org.eclipse.jgit.events.RefsChangedEvent; +import org.eclipse.jgit.transport.ReceiveCommand; + +import com.gitblit.models.RepositoryModel; + +/** + * The event fired by other classes to allow this service to index tickets. + * + * @author James Moger + */ +public class ReceiveCommandEvent extends RefsChangedEvent { + + public final RepositoryModel model; + + public final ReceiveCommand cmd; + + public ReceiveCommandEvent(RepositoryModel model, ReceiveCommand cmd) { + this.model = model; + this.cmd = cmd; + } +}
\ No newline at end of file diff --git a/src/main/java/com/gitblit/service/MirrorService.java b/src/main/java/com/gitblit/service/MirrorService.java index 9833d939..cf9ccb55 100644 --- a/src/main/java/com/gitblit/service/MirrorService.java +++ b/src/main/java/com/gitblit/service/MirrorService.java @@ -28,6 +28,8 @@ import org.eclipse.jgit.lib.RefUpdate.Result; import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.StoredConfig;
import org.eclipse.jgit.transport.FetchResult;
+import org.eclipse.jgit.transport.ReceiveCommand;
+import org.eclipse.jgit.transport.ReceiveCommand.Type;
import org.eclipse.jgit.transport.RemoteConfig;
import org.eclipse.jgit.transport.TrackingRefUpdate;
import org.slf4j.Logger;
@@ -35,9 +37,11 @@ import org.slf4j.LoggerFactory; import com.gitblit.IStoredSettings;
import com.gitblit.Keys;
+import com.gitblit.git.ReceiveCommandEvent;
import com.gitblit.manager.IRepositoryManager;
import com.gitblit.models.RepositoryModel;
import com.gitblit.models.UserModel;
+import com.gitblit.tickets.BranchTicketService;
import com.gitblit.utils.JGitUtils;
/**
@@ -145,6 +149,7 @@ public class MirrorService implements Runnable { FetchResult result = git.fetch().setRemote(mirror.getName()).setDryRun(testing).call();
Collection<TrackingRefUpdate> refUpdates = result.getTrackingRefUpdates();
if (refUpdates.size() > 0) {
+ ReceiveCommand ticketBranchCmd = null;
for (TrackingRefUpdate ru : refUpdates) {
StringBuilder sb = new StringBuilder();
sb.append("updated mirror ");
@@ -161,6 +166,33 @@ public class MirrorService implements Runnable { sb.append("..");
sb.append(ru.getNewObjectId() == null ? "" : ru.getNewObjectId().abbreviate(7).name());
logger.info(sb.toString());
+
+ if (BranchTicketService.BRANCH.equals(ru.getLocalName())) {
+ ReceiveCommand.Type type = null;
+ switch (ru.getResult()) {
+ case NEW:
+ type = Type.CREATE;
+ break;
+ case FAST_FORWARD:
+ type = Type.UPDATE;
+ break;
+ case FORCED:
+ type = Type.UPDATE_NONFASTFORWARD;
+ break;
+ default:
+ type = null;
+ break;
+ }
+
+ if (type != null) {
+ ticketBranchCmd = new ReceiveCommand(ru.getOldObjectId(),
+ ru.getNewObjectId(), ru.getLocalName(), type);
+ }
+ }
+ }
+
+ if (ticketBranchCmd != null) {
+ repository.fireEvent(new ReceiveCommandEvent(model, ticketBranchCmd));
}
}
} catch (Exception e) {
diff --git a/src/main/java/com/gitblit/servlet/RpcServlet.java b/src/main/java/com/gitblit/servlet/RpcServlet.java index 28f0d5bf..2d59ebd7 100644 --- a/src/main/java/com/gitblit/servlet/RpcServlet.java +++ b/src/main/java/com/gitblit/servlet/RpcServlet.java @@ -59,7 +59,7 @@ public class RpcServlet extends JsonServlet { private static final long serialVersionUID = 1L;
- public static final int PROTOCOL_VERSION = 6;
+ public static final int PROTOCOL_VERSION = 7;
private IStoredSettings settings;
@@ -383,6 +383,19 @@ public class RpcServlet extends JsonServlet { } else {
response.sendError(notAllowedCode);
}
+ } else if (RpcRequest.REINDEX_TICKETS.equals(reqType)) {
+ if (allowManagement) {
+ if (StringUtils.isEmpty(objectName)) {
+ // reindex all tickets
+ gitblit.getTicketService().reindex();
+ } else {
+ // reindex tickets in a specific repository
+ RepositoryModel model = gitblit.getRepositoryModel(objectName);
+ gitblit.getTicketService().reindex(model);
+ }
+ } else {
+ response.sendError(notAllowedCode);
+ }
}
// send the result of the request
diff --git a/src/main/java/com/gitblit/tickets/BranchTicketService.java b/src/main/java/com/gitblit/tickets/BranchTicketService.java index 14ed8094..fc0bd8f0 100644 --- a/src/main/java/com/gitblit/tickets/BranchTicketService.java +++ b/src/main/java/com/gitblit/tickets/BranchTicketService.java @@ -27,6 +27,7 @@ import java.util.Map; import java.util.Set; import java.util.TreeSet; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import javax.inject.Inject; @@ -36,6 +37,8 @@ import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.dircache.DirCache; import org.eclipse.jgit.dircache.DirCacheBuilder; import org.eclipse.jgit.dircache.DirCacheEntry; +import org.eclipse.jgit.events.RefsChangedEvent; +import org.eclipse.jgit.events.RefsChangedListener; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.CommitBuilder; import org.eclipse.jgit.lib.FileMode; @@ -48,15 +51,18 @@ import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevTree; import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.transport.ReceiveCommand; import org.eclipse.jgit.treewalk.CanonicalTreeParser; import org.eclipse.jgit.treewalk.TreeWalk; import com.gitblit.Constants; +import com.gitblit.git.ReceiveCommandEvent; import com.gitblit.manager.INotificationManager; import com.gitblit.manager.IRepositoryManager; import com.gitblit.manager.IRuntimeManager; import com.gitblit.manager.IUserManager; import com.gitblit.models.PathModel; +import com.gitblit.models.PathModel.PathChangeModel; import com.gitblit.models.RefModel; import com.gitblit.models.RepositoryModel; import com.gitblit.models.TicketModel; @@ -74,7 +80,7 @@ import com.gitblit.utils.StringUtils; * @author James Moger * */ -public class BranchTicketService extends ITicketService { +public class BranchTicketService extends ITicketService implements RefsChangedListener { public static final String BRANCH = "refs/gitblit/tickets"; @@ -97,6 +103,9 @@ public class BranchTicketService extends ITicketService { repositoryManager); lastAssignedId = new ConcurrentHashMap<String, AtomicLong>(); + + // register the branch ticket service for repository ref changes + Repository.getGlobalListenerList().addRefsChangedListener(this); } @Override @@ -121,6 +130,66 @@ public class BranchTicketService extends ITicketService { } /** + * Listen for tickets branch changes and (re)index tickets, as appropriate + */ + @Override + public synchronized void onRefsChanged(RefsChangedEvent event) { + if (!(event instanceof ReceiveCommandEvent)) { + return; + } + + ReceiveCommandEvent branchUpdate = (ReceiveCommandEvent) event; + RepositoryModel repository = branchUpdate.model; + ReceiveCommand cmd = branchUpdate.cmd; + try { + switch (cmd.getType()) { + case CREATE: + case UPDATE_NONFASTFORWARD: + // reindex everything + reindex(repository); + break; + case UPDATE: + // incrementally index ticket updates + resetCaches(repository); + long start = System.nanoTime(); + log.info("incrementally indexing {} ticket branch due to received ref update", repository.name); + Repository db = repositoryManager.getRepository(repository.name); + try { + Set<Long> ids = new HashSet<Long>(); + List<PathChangeModel> paths = JGitUtils.getFilesInRange(db, + cmd.getOldId().getName(), cmd.getNewId().getName()); + for (PathChangeModel path : paths) { + String name = path.name.substring(path.name.lastIndexOf('/') + 1); + if (!JOURNAL.equals(name)) { + continue; + } + String tid = path.path.split("/")[2]; + long ticketId = Long.parseLong(tid); + if (!ids.contains(ticketId)) { + ids.add(ticketId); + TicketModel ticket = getTicket(repository, ticketId); + log.info(MessageFormat.format("indexing ticket #{0,number,0}: {1}", + ticketId, ticket.title)); + indexer.index(ticket); + } + } + long end = System.nanoTime(); + log.info("incremental indexing of {0} ticket(s) completed in {1} msecs", + ids.size(), TimeUnit.NANOSECONDS.toMillis(end - start)); + } finally { + db.close(); + } + break; + default: + log.warn("Unexpected receive type {} in BranchTicketService.onRefsChanged" + cmd.getType()); + break; + } + } catch (Exception e) { + log.error("failed to reindex " + repository.name, e); + } + } + + /** * Returns a RefModel for the refs/gitblit/tickets branch in the repository. * If the branch can not be found, null is returned. * diff --git a/src/main/java/com/gitblit/tickets/ITicketService.java b/src/main/java/com/gitblit/tickets/ITicketService.java index d04cd5e1..90f9c6dd 100644 --- a/src/main/java/com/gitblit/tickets/ITicketService.java +++ b/src/main/java/com/gitblit/tickets/ITicketService.java @@ -897,6 +897,7 @@ public abstract class ITicketService { public boolean deleteAll(RepositoryModel repository) { boolean success = deleteAllImpl(repository); if (success) { + log.info("Deleted all tickets for {}", repository.name); resetCaches(repository); indexer.deleteAll(repository); } @@ -936,6 +937,8 @@ public abstract class ITicketService { TicketModel ticket = getTicket(repository, ticketId); boolean success = deleteTicketImpl(repository, ticket, deletedBy); if (success) { + log.info(MessageFormat.format("Deleted {0} ticket #{1,number,0}: {2}", + repository.name, ticketId, ticket.title)); ticketsCache.invalidate(new TicketKey(repository, ticketId)); indexer.delete(ticket); return true; @@ -1074,6 +1077,7 @@ public abstract class ITicketService { long end = System.nanoTime(); long secs = TimeUnit.NANOSECONDS.toMillis(end - start); log.info("reindexing completed in {} msecs.", secs); + resetCaches(repository); } /** diff --git a/src/main/java/com/gitblit/utils/RpcUtils.java b/src/main/java/com/gitblit/utils/RpcUtils.java index 24e07dcd..5e577fb6 100644 --- a/src/main/java/com/gitblit/utils/RpcUtils.java +++ b/src/main/java/com/gitblit/utils/RpcUtils.java @@ -252,6 +252,37 @@ public class RpcUtils { }
/**
+ * Reindex all tickets on the Gitblit server.
+ *
+ * @param serverUrl
+ * @param account
+ * @param password
+ * @return true if the action succeeded
+ * @throws IOException
+ */
+ public static boolean reindexTickets(String serverUrl, String account,
+ char[] password) throws IOException {
+ return doAction(RpcRequest.REINDEX_TICKETS, null, null, serverUrl, account,
+ password);
+ }
+
+ /**
+ * Reindex tickets for the specified repository on the Gitblit server.
+ *
+ * @param serverUrl
+ * @param repositoryName
+ * @param account
+ * @param password
+ * @return true if the action succeeded
+ * @throws IOException
+ */
+ public static boolean reindexTickets(String serverUrl, String repositoryName,
+ String account, char[] password) throws IOException {
+ return doAction(RpcRequest.REINDEX_TICKETS, repositoryName, null, serverUrl,
+ account, password);
+ }
+
+ /**
* Create a user on the Gitblit server.
*
* @param user
diff --git a/src/site/rpc.mkd b/src/site/rpc.mkd index 58b2966c..b86fd9ad 100644 --- a/src/site/rpc.mkd +++ b/src/site/rpc.mkd @@ -59,6 +59,7 @@ The Gitblit API includes methods for retrieving and interpreting RSS feeds. The <tr><td>Gitblit v1.1.0</td><td>4</td></tr>
<tr><td>Gitblit v1.2.0+</td><td>5</td></tr>
<tr><td>Gitblit v1.3.1+</td><td>6</td></tr>
+<tr><td>Gitblit v1.4.0+</td><td>7</td></tr>
</tbody>
</table>
@@ -102,6 +103,7 @@ Use *SET_REPOSITORY_TEAM_PERMISSIONS* instead. <tr><td>SET_REPOSITORY_TEAM_PERMISSIONS</td><td>repository name</td><td><em>admin</em></td><td>5</td><td>List<String></td><td>-</td></tr>
<tr><td>LIST_SETTINGS</td><td>-</td><td><em>admin</em></td><td>1</td><td>-</td><td>ServerSettings (management keys)</td></tr>
<tr><td>CLEAR_REPOSITORY_CACHE</td><td>-</td><td><em>-</em></td><td>4</td><td>-</td><td>-</td></tr>
+<tr><td>REINDEX_TICKETS</td><td>repository name</td><td><em>-</em></td><td>7</td><td>-</td><td>-</td></tr>
<tr><td colspan='6'><em>web.enableRpcAdministration=true</em></td></tr>
<tr><td>LIST_FEDERATION_REGISTRATIONS</td><td>-</td><td><em>admin</em></td><td>1</td><td>-</td><td>List<FederationModel></td></tr>
<tr><td>LIST_FEDERATION_RESULTS</td><td>-</td><td><em>admin</em></td><td>1</td><td>-</td><td>List<FederationModel></td></tr>
diff --git a/src/site/tickets_replication.mkd b/src/site/tickets_replication.mkd new file mode 100644 index 00000000..542fd5fc --- /dev/null +++ b/src/site/tickets_replication.mkd @@ -0,0 +1,135 @@ +## Ticket Replication & Advanced Administration + +*SINCE 1.4.0* + +**Ticket Replication** +Gitblit does *not* provide a generic/universal replication mechanism that works across all persistence backends. + +**Advanced Administration** +Gitblit does *not* provide a generic/universal for advanced administration (i.e. manually tweaking ticket data) however each service does have a strategy for that case. + +### FileTicketService + +#### Ticket Replication +Replication is not supported. + +#### Advanced Administration +Use your favorite text editor to **carefully** manipulate a ticket's journal file. I recommend using a JSON validation service to ensure your changes are valid JSON. + +After you've done this, you will need to reset Gitblit's internal ticket cache and you may need to reindex the tickets, depending on your changes. + +### BranchTicketService + +#### Ticket Replication +Gitblit supports ticket replication for a couple of scenarios with the *BranchTicketService*. This requires that the Gitblit instance receiving the ticket data be configured for the *BranchTicketService*. Likewise, the source of the ticket data must be a repository that has ticket data persisted using the *BranchTicketService*. + +##### Manually Pushing refs/gitblit/tickets + +Let's say you wanted to create a perfect clone of the Gitblit repository hosted at https://dev.gitblit.com in your own Gitblit instance. We'll use this repository as an example because it is configured for the *BranchTicketService*. + +**Assumptions** + +1. We are pushing to our local Gitblit with the admin account, or some other privileged account +2. Our local Gitblit is configured for create-on-push +3. Our local Gitblit is configured for the *BranchTicketService* + +**Procedure** + +1. First we'll clone a mirror of the source repository:<pre>git clone --mirror https://dev.gitblit.com/r/gitblit.git </pre> +2. Then we'll add a remote for our local Gitblit instance:<pre>cd gitblit.git<br/>git remote add local https://localhost:8443/gitblit.git </pre> +3. Then we'll push *everything* to our local Gitblit:<pre>git push --mirror local</pre> + +If your push was successful you should have a new repository with the entire official Gitblit tickets data. + +##### Mirroring refs/gitblit/tickets + +Gitblit 1.4.0 introduces a mirroring service. This is not the same as the federation feature - although there are similarities. + +If you setup a mirror of another Gitblit repository which uses the *BranchTicketService* **AND** your Gitblit instance is configured for *BranchTicketService*, then your Gitblit will automatically fetch and reindex all tickets without intervention or further configuration. + +**Things to note about mirrors...** + +1. You must set *git.enableMirroring=true* and optionally change *git.mirrorPeriod* +2. Mirrors are read-only. You can not push to a mirror. You can not manipulate a mirror's ticket data. +3. Mirrors are a Git feature - not a Gitblit invention. To create one you must currently use Git within your *git.repositoriesFolder*, you must reset your cache, and you must trigger a ticket reindex.<pre>git clone --mirror <url><br/>curl --insecure --user admin:admin "https://localhost:8443/rpc?req=clear_repository_cache"<br/>curl --insecure --user admin:admin "https://localhost:8443/rpc?req=reindex_tickets&name=<repo>"</pre> +4. After you have indexed the repository, Gitblit will take over and incrementally update your tickets data on each fetch. + +#### Advanced Administration +Repository owners or Gitblit administrators have the option of manually editing ticket data. To do this you must fetch and checkout the `refs/gitblit/tickets` ref. This orphan branch is where ticket data is stored. You may then use a text editor to **carefully** manipulate journals and push your changes back upstream. I recommend using a JSON validation tool to ensure your changes are valid JSON. + + git fetch origin refs/gitblit/tickets + git checkout -B tix FETCH_HEAD + ...fix data... + git add . + git commit + git push origin HEAD:refs/gitblit/tickets + +Gitblit will identify the incoming `refs/gitblit/tickets` ref update and will incrementally index the changed tickets OR, if the update is non-fast-forward, all tickets on that branch will be reindexed. + +### RedisTicketService + +#### Ticket Replication +Redis is capable of sophisticated replication and clustering. I have not configured Redis replication myself. If this topic interests you please document your procedure and open a pull request to improve this section for others who may also be interested in Redis replication. + +#### Advanced Administration +You can directly manipulate the journals in Redis. The most convenient way do manipulate data is using the simple, but very competent, [RedisDesktopManager](http://redisdesktop.com). It even provides JSON pretty printing which faciliates editing. + +After you've done this, you will need to reset Gitblit's internal ticket cache and you may need to reindex the tickets, depending on your changes. + +The schema of the Redis backend looks like this *repository:object:id*. + + redis 127.0.0.1:6379> keys * + 1) "~james/mytickets.git:ticket:8" + 2) "~james/mytickets.git:journal:8" + 3) "~james/mytickets.git:ticket:4" + 4) "~james/mytickets.git:counter" + 5) "~james/mytickets.git:journal:2" + 6) "~james/mytickets.git:journal:4" + 7) "~james/mytickets.git:journal:7" + 8) "~james/mytickets.git:ticket:3" + 9) "~james/mytickets.git:ticket:6" + 10) "~james/mytickets.git:journal:1" + 11) "~james/mytickets.git:ticket:2" + 12) "~james/mytickets.git:journal:6" + 13) "~james/mytickets.git:ticket:7" + 14) "~james/mytickets.git:ticket:1" + 15) "~james/mytickets.git:journal:3" + +**Some notes about the Redis backend** +The *ticket* object keys are provided as a convenience for integration with other systems. Gitblit does not read those keys, but it does update them. + +The *journal* object keys are the important ones. Gitblit maintains ticket change journals. The *journal* object keys are Redis LISTs where each list entry is a JSON change document. + +The other important object key is the *counter* which is used to assign ticket ids. + +### Resetting the Tickets Cache and Reindexing Tickets + +Reindexing can be memory exhaustive. It obviously depends on the number of tickets you have. Normally, you won't need to manually reindex but if you do, offline reindexing is recommended. + +#### Offline Reindexing + +##### Gitblit GO + +Gitblit GO ships with a script that executes the *com.gitblit.ReindexTickets* tool included in the Gitblit jar file. This tool will reindex *all* tickets in *all* repositories **AND** must be run when Gitblit is offline. + + reindex-tickets <baseFolder> + +##### Gitblit WAR/Express + +Gitblit WAR/Express does not ship with anything other than the WAR, but you can still reindex tickets offline with a little extra effort. + +*Windows* + + java -cp "C:/path/to/WEB-INF/lib/*" com.gitblit.ReindexTickets --baseFolder <baseFolder> + +*Linux/Unix/Mac OSX* + + java -cp /path/to/WEB-INF/lib/* com.gitblit.ReindexTickets --baseFolder <baseFolder> + +#### Live Reindexing + +You can trigger a live reindex of tickets for any backend using Gitblit's RPC interface and curl or your browser. This will also reset Gitblit's internal ticket cache. Use of this RPC requires *web.enableRpcServlet=true* and *web.enableRpcManagement=true* along with administrator credentials. + + curl --insecure --user admin:admin "https://localhost:8443/rpc?req=reindex_tickets" + curl --insecure --user admin:admin "https://localhost:8443/rpc?req=reindex_tickets&name=gitblit.git" + |