Skip to content

Commit 2754961

Browse files
committed
Document edit capability via ProseMirror submodule gitblit-org#974
+ New docEdit page with links from docPage and docList + Bespoke menu system with full screen edit mode + npm required for building client side scripts + Ant script added for BuildUI which performs npm commands + Update font-awesome to 4.5.0 + Factor out to JGitUtils common code in BranchTicketService for EditFilePage + getTreeEntries + commitIndex + Merge capability for document editing
1 parent 86401c3 commit 2754961

25 files changed

+1106
-150
lines changed

.gitmodules

+3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
[submodule "src/main/distrib/data/gitignore"]
22
path = src/main/distrib/data/gitignore
33
url = https://github.com/github/gitignore.git
4+
[submodule "src/main/js/prosemirror"]
5+
path = src/main/js/prosemirror
6+
url = https://github.com/ProseMirror/prosemirror.git

build.xml

+17
Original file line numberDiff line numberDiff line change
@@ -1056,4 +1056,21 @@
10561056
<mx:install />
10571057
</target>
10581058

1059+
1060+
<!--
1061+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1062+
Build Gitblit UI via npm
1063+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1064+
-->
1065+
<target name="buildUI" description="Build Gitblit UI via npm">
1066+
<exec executable="npm" dir="src/main/js/" failonerror="true" vmlauncher="false" searchpath="true" >
1067+
<arg value="install" />
1068+
</exec>
1069+
1070+
<exec executable="npm" dir="src/main/js/" failonerror="true" vmlauncher="false" searchpath="true" >
1071+
<arg value="run" />
1072+
<arg value="build" />
1073+
</exec>
1074+
</target>
1075+
10591076
</project>

src/main/java/com/gitblit/tickets/BranchTicketService.java

+7-102
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
import java.text.MessageFormat;
2020
import java.util.ArrayList;
2121
import java.util.Arrays;
22-
import java.util.Collection;
2322
import java.util.Collections;
2423
import java.util.HashSet;
2524
import java.util.List;
@@ -31,21 +30,17 @@
3130
import java.util.concurrent.atomic.AtomicLong;
3231

3332
import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException;
34-
import org.eclipse.jgit.api.errors.JGitInternalException;
3533
import org.eclipse.jgit.dircache.DirCache;
3634
import org.eclipse.jgit.dircache.DirCacheBuilder;
3735
import org.eclipse.jgit.dircache.DirCacheEntry;
3836
import org.eclipse.jgit.events.RefsChangedEvent;
3937
import org.eclipse.jgit.events.RefsChangedListener;
40-
import org.eclipse.jgit.internal.JGitText;
41-
import org.eclipse.jgit.lib.CommitBuilder;
4238
import org.eclipse.jgit.lib.FileMode;
4339
import org.eclipse.jgit.lib.ObjectId;
4440
import org.eclipse.jgit.lib.ObjectInserter;
4541
import org.eclipse.jgit.lib.PersonIdent;
4642
import org.eclipse.jgit.lib.Ref;
4743
import org.eclipse.jgit.lib.RefRename;
48-
import org.eclipse.jgit.lib.RefUpdate;
4944
import org.eclipse.jgit.lib.RefUpdate.Result;
5045
import org.eclipse.jgit.lib.Repository;
5146
import org.eclipse.jgit.revwalk.RevCommit;
@@ -338,7 +333,7 @@ private void writeTicketsFile(Repository db, String file, String content, String
338333
Set<String> ignorePaths = new HashSet<String>();
339334
ignorePaths.add(file);
340335

341-
for (DirCacheEntry entry : getTreeEntries(db, ignorePaths)) {
336+
for (DirCacheEntry entry : JGitUtils.getTreeEntries(db, BRANCH, ignorePaths)) {
342337
builder.add(entry);
343338
}
344339

@@ -804,7 +799,7 @@ private DirCache createIndex(Repository db, long ticketId, Change change)
804799
}
805800
}
806801

807-
for (DirCacheEntry entry : getTreeEntries(db, ignorePaths)) {
802+
for (DirCacheEntry entry : JGitUtils.getTreeEntries(db, BRANCH, ignorePaths)) {
808803
builder.add(entry);
809804
}
810805

@@ -816,108 +811,18 @@ private DirCache createIndex(Repository db, long ticketId, Change change)
816811
return newIndex;
817812
}
818813

819-
/**
820-
* Returns all tree entries that do not match the ignore paths.
821-
*
822-
* @param db
823-
* @param ignorePaths
824-
* @param dcBuilder
825-
* @throws IOException
826-
*/
827-
private List<DirCacheEntry> getTreeEntries(Repository db, Collection<String> ignorePaths) throws IOException {
828-
List<DirCacheEntry> list = new ArrayList<DirCacheEntry>();
829-
TreeWalk tw = null;
830-
try {
831-
ObjectId treeId = db.resolve(BRANCH + "^{tree}");
832-
if (treeId == null) {
833-
// branch does not exist yet, could be migrating tickets
834-
return list;
835-
}
836-
tw = new TreeWalk(db);
837-
int hIdx = tw.addTree(treeId);
838-
tw.setRecursive(true);
839-
840-
while (tw.next()) {
841-
String path = tw.getPathString();
842-
CanonicalTreeParser hTree = null;
843-
if (hIdx != -1) {
844-
hTree = tw.getTree(hIdx, CanonicalTreeParser.class);
845-
}
846-
if (!ignorePaths.contains(path)) {
847-
// add all other tree entries
848-
if (hTree != null) {
849-
final DirCacheEntry entry = new DirCacheEntry(path);
850-
entry.setObjectId(hTree.getEntryObjectId());
851-
entry.setFileMode(hTree.getEntryFileMode());
852-
list.add(entry);
853-
}
854-
}
855-
}
856-
} finally {
857-
if (tw != null) {
858-
tw.close();
859-
}
860-
}
861-
return list;
862-
}
863-
864814
private boolean commitIndex(Repository db, DirCache index, String author, String message) throws IOException, ConcurrentRefUpdateException {
815+
final boolean forceCommit = true;
865816
boolean success = false;
866-
817+
867818
ObjectId headId = db.resolve(BRANCH + "^{commit}");
868819
if (headId == null) {
869820
// create the branch
870821
createTicketsBranch(db);
871-
headId = db.resolve(BRANCH + "^{commit}");
872-
}
873-
ObjectInserter odi = db.newObjectInserter();
874-
try {
875-
// Create the in-memory index of the new/updated ticket
876-
ObjectId indexTreeId = index.writeTree(odi);
877-
878-
// Create a commit object
879-
PersonIdent ident = new PersonIdent(author, "gitblit@localhost");
880-
CommitBuilder commit = new CommitBuilder();
881-
commit.setAuthor(ident);
882-
commit.setCommitter(ident);
883-
commit.setEncoding(Constants.ENCODING);
884-
commit.setMessage(message);
885-
commit.setParentId(headId);
886-
commit.setTreeId(indexTreeId);
887-
888-
// Insert the commit into the repository
889-
ObjectId commitId = odi.insert(commit);
890-
odi.flush();
891-
892-
RevWalk revWalk = new RevWalk(db);
893-
try {
894-
RevCommit revCommit = revWalk.parseCommit(commitId);
895-
RefUpdate ru = db.updateRef(BRANCH);
896-
ru.setNewObjectId(commitId);
897-
ru.setExpectedOldObjectId(headId);
898-
ru.setRefLogMessage("commit: " + revCommit.getShortMessage(), false);
899-
Result rc = ru.forceUpdate();
900-
switch (rc) {
901-
case NEW:
902-
case FORCED:
903-
case FAST_FORWARD:
904-
success = true;
905-
break;
906-
case REJECTED:
907-
case LOCK_FAILURE:
908-
throw new ConcurrentRefUpdateException(JGitText.get().couldNotLockHEAD,
909-
ru.getRef(), rc);
910-
default:
911-
throw new JGitInternalException(MessageFormat.format(
912-
JGitText.get().updatingRefFailed, BRANCH, commitId.toString(),
913-
rc));
914-
}
915-
} finally {
916-
revWalk.close();
917-
}
918-
} finally {
919-
odi.close();
920822
}
823+
824+
success = JGitUtils.commitIndex(db, BRANCH, index, headId, forceCommit, author, "gitblit@localhost", message);
825+
921826
return success;
922827
}
923828

src/main/java/com/gitblit/utils/JGitUtils.java

+131
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import java.text.MessageFormat;
2222
import java.util.ArrayList;
2323
import java.util.Arrays;
24+
import java.util.Collection;
2425
import java.util.Collections;
2526
import java.util.Date;
2627
import java.util.HashMap;
@@ -36,16 +37,21 @@
3637
import org.eclipse.jgit.api.FetchCommand;
3738
import org.eclipse.jgit.api.Git;
3839
import org.eclipse.jgit.api.TagCommand;
40+
import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException;
3941
import org.eclipse.jgit.api.errors.GitAPIException;
42+
import org.eclipse.jgit.api.errors.JGitInternalException;
4043
import org.eclipse.jgit.diff.DiffEntry;
4144
import org.eclipse.jgit.diff.DiffEntry.ChangeType;
4245
import org.eclipse.jgit.diff.DiffFormatter;
4346
import org.eclipse.jgit.diff.RawTextComparator;
47+
import org.eclipse.jgit.dircache.DirCache;
48+
import org.eclipse.jgit.dircache.DirCacheEntry;
4449
import org.eclipse.jgit.errors.ConfigInvalidException;
4550
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
4651
import org.eclipse.jgit.errors.LargeObjectException;
4752
import org.eclipse.jgit.errors.MissingObjectException;
4853
import org.eclipse.jgit.errors.StopWalkException;
54+
import org.eclipse.jgit.internal.JGitText;
4955
import org.eclipse.jgit.lib.BlobBasedConfig;
5056
import org.eclipse.jgit.lib.CommitBuilder;
5157
import org.eclipse.jgit.lib.Constants;
@@ -63,6 +69,7 @@
6369
import org.eclipse.jgit.lib.TreeFormatter;
6470
import org.eclipse.jgit.merge.MergeStrategy;
6571
import org.eclipse.jgit.merge.RecursiveMerger;
72+
import org.eclipse.jgit.merge.ThreeWayMerger;
6673
import org.eclipse.jgit.revwalk.RevBlob;
6774
import org.eclipse.jgit.revwalk.RevCommit;
6875
import org.eclipse.jgit.revwalk.RevObject;
@@ -75,6 +82,7 @@
7582
import org.eclipse.jgit.transport.CredentialsProvider;
7683
import org.eclipse.jgit.transport.FetchResult;
7784
import org.eclipse.jgit.transport.RefSpec;
85+
import org.eclipse.jgit.treewalk.CanonicalTreeParser;
7886
import org.eclipse.jgit.treewalk.TreeWalk;
7987
import org.eclipse.jgit.treewalk.filter.AndTreeFilter;
8088
import org.eclipse.jgit.treewalk.filter.OrTreeFilter;
@@ -2597,4 +2605,127 @@ public static String getLfsRepositoryUrl(String baseURL, String repositoryName,
25972605
+ "objects/" + oid;
25982606

25992607
}
2608+
2609+
/**
2610+
* Returns all tree entries that do not match the ignore paths.
2611+
*
2612+
* @param db
2613+
* @param ignorePaths
2614+
* @param dcBuilder
2615+
* @throws IOException
2616+
*/
2617+
public static List<DirCacheEntry> getTreeEntries(Repository db, String branch, Collection<String> ignorePaths) throws IOException {
2618+
List<DirCacheEntry> list = new ArrayList<DirCacheEntry>();
2619+
TreeWalk tw = null;
2620+
try {
2621+
ObjectId treeId = db.resolve(branch + "^{tree}");
2622+
if (treeId == null) {
2623+
// branch does not exist yet
2624+
return list;
2625+
}
2626+
tw = new TreeWalk(db);
2627+
int hIdx = tw.addTree(treeId);
2628+
tw.setRecursive(true);
2629+
2630+
while (tw.next()) {
2631+
String path = tw.getPathString();
2632+
CanonicalTreeParser hTree = null;
2633+
if (hIdx != -1) {
2634+
hTree = tw.getTree(hIdx, CanonicalTreeParser.class);
2635+
}
2636+
if (!ignorePaths.contains(path)) {
2637+
// add all other tree entries
2638+
if (hTree != null) {
2639+
final DirCacheEntry entry = new DirCacheEntry(path);
2640+
entry.setObjectId(hTree.getEntryObjectId());
2641+
entry.setFileMode(hTree.getEntryFileMode());
2642+
list.add(entry);
2643+
}
2644+
}
2645+
}
2646+
} finally {
2647+
if (tw != null) {
2648+
tw.close();
2649+
}
2650+
}
2651+
return list;
2652+
}
2653+
2654+
public static boolean commitIndex(Repository db, String branch, DirCache index,
2655+
ObjectId parentId, boolean forceCommit,
2656+
String author, String authorEmail, String message) throws IOException, ConcurrentRefUpdateException {
2657+
boolean success = false;
2658+
2659+
ObjectId headId = db.resolve(branch + "^{commit}");
2660+
ObjectId baseId = parentId;
2661+
if (baseId == null || headId == null) { return false; }
2662+
2663+
ObjectInserter odi = db.newObjectInserter();
2664+
try {
2665+
// Create the in-memory index of the new/updated ticket
2666+
ObjectId indexTreeId = index.writeTree(odi);
2667+
2668+
// Create a commit object
2669+
PersonIdent ident = new PersonIdent(author, authorEmail);
2670+
2671+
if (forceCommit == false) {
2672+
ThreeWayMerger merger = MergeStrategy.RECURSIVE.newMerger(db, true);
2673+
merger.setObjectInserter(odi);
2674+
merger.setBase(baseId);
2675+
boolean mergeSuccess = merger.merge(indexTreeId, headId);
2676+
2677+
if (mergeSuccess) {
2678+
indexTreeId = merger.getResultTreeId();
2679+
} else {
2680+
//Manual merge required
2681+
return false;
2682+
}
2683+
}
2684+
2685+
CommitBuilder commit = new CommitBuilder();
2686+
commit.setAuthor(ident);
2687+
commit.setCommitter(ident);
2688+
commit.setEncoding(com.gitblit.Constants.ENCODING);
2689+
commit.setMessage(message);
2690+
commit.setParentId(headId);
2691+
commit.setTreeId(indexTreeId);
2692+
2693+
// Insert the commit into the repository
2694+
ObjectId commitId = odi.insert(commit);
2695+
odi.flush();
2696+
2697+
RevWalk revWalk = new RevWalk(db);
2698+
try {
2699+
RevCommit revCommit = revWalk.parseCommit(commitId);
2700+
RefUpdate ru = db.updateRef(branch);
2701+
ru.setForceUpdate(forceCommit);
2702+
ru.setNewObjectId(commitId);
2703+
ru.setExpectedOldObjectId(headId);
2704+
ru.setRefLogMessage("commit: " + revCommit.getShortMessage(), false);
2705+
Result rc = ru.update();
2706+
2707+
switch (rc) {
2708+
case NEW:
2709+
case FORCED:
2710+
case FAST_FORWARD:
2711+
success = true;
2712+
break;
2713+
case REJECTED:
2714+
case LOCK_FAILURE:
2715+
throw new ConcurrentRefUpdateException(JGitText.get().couldNotLockHEAD,
2716+
ru.getRef(), rc);
2717+
default:
2718+
throw new JGitInternalException(MessageFormat.format(
2719+
JGitText.get().updatingRefFailed, branch, commitId.toString(),
2720+
rc));
2721+
}
2722+
} finally {
2723+
revWalk.close();
2724+
}
2725+
} finally {
2726+
odi.close();
2727+
}
2728+
return success;
2729+
}
2730+
26002731
}

src/main/java/com/gitblit/wicket/pages/DocPage.html

+2-2
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
<div class="docs" style="margin-top: -10px;">
1313
<!-- doc nav links -->
1414
<div style="float: right;" class="docnav">
15-
<a wicket:id="blameLink"><wicket:message key="gb.blame"></wicket:message></a> | <a wicket:id="historyLink"><wicket:message key="gb.history"></wicket:message></a> | <a wicket:id="rawLink"><wicket:message key="gb.raw"></wicket:message></a>
15+
<a wicket:id="editLink"><wicket:message key="gb.edit"></wicket:message></a> | <a wicket:id="blameLink"><wicket:message key="gb.blame"></wicket:message></a> | <a wicket:id="historyLink"><wicket:message key="gb.history"></wicket:message></a> | <a wicket:id="rawLink"><wicket:message key="gb.raw"></wicket:message></a>
1616
</div>
1717

1818
<!-- document content -->
@@ -24,7 +24,7 @@
2424
<div class="docs">
2525
<!-- doc nav links -->
2626
<div style="float: right;" class="docnav">
27-
<a wicket:id="blameLink"><wicket:message key="gb.blame"></wicket:message></a> | <a wicket:id="historyLink"><wicket:message key="gb.history"></wicket:message></a> | <a wicket:id="rawLink"><wicket:message key="gb.raw"></wicket:message></a>
27+
<a wicket:id="editLink"><wicket:message key="gb.edit"></wicket:message></a> | <a wicket:id="blameLink"><wicket:message key="gb.blame"></wicket:message></a> | <a wicket:id="historyLink"><wicket:message key="gb.history"></wicket:message></a> | <a wicket:id="rawLink"><wicket:message key="gb.raw"></wicket:message></a>
2828
</div>
2929

3030
<!-- document content -->

src/main/java/com/gitblit/wicket/pages/DocPage.java

+2
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ public DocPage(PageParameters params) {
8585
}
8686

8787
// document page links
88+
fragment.add(new BookmarkablePageLink<Void>("editLink", EditFilePage.class,
89+
WicketUtils.newPathParameter(repositoryName, objectId, documentPath)));
8890
fragment.add(new BookmarkablePageLink<Void>("blameLink", BlamePage.class,
8991
WicketUtils.newPathParameter(repositoryName, objectId, documentPath)));
9092
fragment.add(new BookmarkablePageLink<Void>("historyLink", HistoryPage.class,

0 commit comments

Comments
 (0)