Merged #22 "Tie mirroring, pushing, and the BranchTicketService together"
2 files added
12 files modified
| | |
| | | <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" />
|
| | |
| | | <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" />
|
| | |
| | | # |
| | | # -------------------------------------------------------------------------- |
| | | |
| | | 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 |
| | | |
| | |
| | | @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 |
| | |
| | | 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,
|
| | |
| | | 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));
|
| | |
| | | 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;
|
| | |
| | |
|
| | | 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);
|
| | |
| | | /** 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;
|
| | | }
|
| | | }
|
| | |
|
| | |
| | | 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;
|
| | |
| | | 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);
|
| | | }
|
| | | }
|
| | | }
|
| | |
| | | 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:
|
| | |
| | | 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.");
|
New file |
| | |
| | | /* |
| | | * 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; |
| | | } |
| | | } |
| | |
| | | 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;
|
| | |
| | |
|
| | | 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;
|
| | |
|
| | | /**
|
| | |
| | | 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 ");
|
| | |
| | | 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) {
|
| | |
| | |
|
| | | private static final long serialVersionUID = 1L;
|
| | |
|
| | | public static final int PROTOCOL_VERSION = 6;
|
| | | public static final int PROTOCOL_VERSION = 7;
|
| | |
|
| | | private IStoredSettings settings;
|
| | |
|
| | |
| | | } 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
|
| | |
| | | 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; |
| | |
| | | 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; |
| | |
| | | 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; |
| | |
| | | * @author James Moger |
| | | * |
| | | */ |
| | | public class BranchTicketService extends ITicketService { |
| | | public class BranchTicketService extends ITicketService implements RefsChangedListener { |
| | | |
| | | public static final String BRANCH = "refs/gitblit/tickets"; |
| | | |
| | |
| | | repositoryManager); |
| | | |
| | | lastAssignedId = new ConcurrentHashMap<String, AtomicLong>(); |
| | | |
| | | // register the branch ticket service for repository ref changes |
| | | Repository.getGlobalListenerList().addRefsChangedListener(this); |
| | | } |
| | | |
| | | @Override |
| | |
| | | } |
| | | |
| | | /** |
| | | * 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. |
| | | * |
| | |
| | | public boolean deleteAll(RepositoryModel repository) { |
| | | boolean success = deleteAllImpl(repository); |
| | | if (success) { |
| | | log.info("Deleted all tickets for {}", repository.name); |
| | | resetCaches(repository); |
| | | indexer.deleteAll(repository); |
| | | } |
| | |
| | | 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; |
| | |
| | | long end = System.nanoTime(); |
| | | long secs = TimeUnit.NANOSECONDS.toMillis(end - start); |
| | | log.info("reindexing completed in {} msecs.", secs); |
| | | resetCaches(repository); |
| | | } |
| | | |
| | | /** |
| | |
| | | }
|
| | |
|
| | | /**
|
| | | * 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
|
| | |
| | | <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>
|
| | |
|
| | |
| | | <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>
|
New file |
| | |
| | | ## 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" |
| | | |