Reject disconnected ancestries when creating changes

If we are asked to create new changes in a project, require that the
new commit objects be connected to the existing commits already in
the refs/heads/ or refs/tags/ namespaces of the destination.  This is
a simple check to catch users who mistype the URL on their push
command line and send objects into the wrong repository by mistake.

Suggested-in: http://groups.google.com/group/repo-discuss/browse_thread/thread/61bdc30dbc2c3d87
Signed-off-by: Shawn O. Pearce <sop@google.com>
CC: Simon Wilkinson <simon@sxw.org.uk>
This commit is contained in:
Shawn O. Pearce 2009-08-20 08:47:29 -07:00
parent ebb4e64cfc
commit 5169e14558

View File

@ -67,6 +67,7 @@ import org.spearce.jgit.lib.RefUpdate;
import org.spearce.jgit.revwalk.FooterKey; import org.spearce.jgit.revwalk.FooterKey;
import org.spearce.jgit.revwalk.FooterLine; import org.spearce.jgit.revwalk.FooterLine;
import org.spearce.jgit.revwalk.RevCommit; import org.spearce.jgit.revwalk.RevCommit;
import org.spearce.jgit.revwalk.RevFlag;
import org.spearce.jgit.revwalk.RevObject; import org.spearce.jgit.revwalk.RevObject;
import org.spearce.jgit.revwalk.RevSort; import org.spearce.jgit.revwalk.RevSort;
import org.spearce.jgit.revwalk.RevTag; import org.spearce.jgit.revwalk.RevTag;
@ -195,7 +196,10 @@ final class Receive extends AbstractGitCommand {
public void onPreReceive(final ReceivePack arg0, public void onPreReceive(final ReceivePack arg0,
final Collection<ReceiveCommand> commands) { final Collection<ReceiveCommand> commands) {
parseCommands(commands); parseCommands(commands);
if (newChange != null
&& newChange.getResult() == ReceiveCommand.Result.NOT_ATTEMPTED) {
createNewChanges(); createNewChanges();
}
appendPatchSets(); appendPatchSets();
} }
}); });
@ -527,6 +531,60 @@ final class Receive extends AbstractGitCommand {
reject(cmd, "branch " + n + " not found"); reject(cmd, "branch " + n + " not found");
return; return;
} }
// Validate that the new commits are connected with the existing heads
// or tags of this repository. If they aren't, we want to abort. We do
// this check by coloring the tip CONNECTED and letting a RevWalk push
// that color through the graph until it reaches at least one of our
// already existing heads or tags. We then test to see if that color
// made it back onto that set.
//
try {
final RevWalk walk = rp.getRevWalk();
final RevFlag CONNECTED = walk.newFlag("CONNECTED");
walk.carry(CONNECTED);
walk.reset();
walk.sort(RevSort.TOPO);
walk.sort(RevSort.REVERSE, true);
final RevCommit tip = walk.parseCommit(newChange.getNewId());
tip.add(CONNECTED);
walk.markStart(tip);
final List<RevCommit> heads = new ArrayList<RevCommit>();
for (final Ref r : rp.getAdvertisedRefs().values()) {
if (isHead(r) || isTag(r)) {
try {
final RevCommit h = walk.parseCommit(r.getObjectId());
walk.markUninteresting(h);
heads.add(h);
} catch (IOException e) {
continue;
}
}
}
if (!heads.isEmpty()) {
while (walk.next() != null) {
/* nothing, just pump the RevWalk until its done */
}
boolean isConnected = false;
for (final RevCommit c : heads) {
if (c.has(CONNECTED)) {
isConnected = true;
break;
}
}
if (!isConnected) {
reject(newChange, "no common ancestry");
return;
}
}
} catch (IOException e) {
newChange.setResult(Result.REJECTED_MISSING_OBJECT);
log.error("Invalid pack upload; one or more objects weren't sent", e);
return;
}
} }
private void parseNewPatchSetCommand(final ReceiveCommand cmd, private void parseNewPatchSetCommand(final ReceiveCommand cmd,
@ -572,11 +630,6 @@ final class Receive extends AbstractGitCommand {
} }
private void createNewChanges() { private void createNewChanges() {
if (newChange == null
|| newChange.getResult() != ReceiveCommand.Result.NOT_ATTEMPTED) {
return;
}
final List<RevCommit> toCreate = new ArrayList<RevCommit>(); final List<RevCommit> toCreate = new ArrayList<RevCommit>();
final RevWalk walk = rp.getRevWalk(); final RevWalk walk = rp.getRevWalk();
walk.reset(); walk.reset();
@ -603,6 +656,8 @@ final class Receive extends AbstractGitCommand {
continue; continue;
} }
if (!validCommitter(newChange, c)) { if (!validCommitter(newChange, c)) {
// Not a change the user can propose? Abort as early as possible.
//
return; return;
} }
toCreate.add(c); toCreate.add(c);
@ -1254,6 +1309,10 @@ final class Receive extends AbstractGitCommand {
cmd.setResult(ReceiveCommand.Result.REJECTED_OTHER_REASON, why); cmd.setResult(ReceiveCommand.Result.REJECTED_OTHER_REASON, why);
} }
private static boolean isTag(final Ref ref) {
return ref.getName().startsWith(Constants.R_TAGS);
}
private static boolean isTag(final ReceiveCommand cmd) { private static boolean isTag(final ReceiveCommand cmd) {
return cmd.getRefName().startsWith(Constants.R_TAGS); return cmd.getRefName().startsWith(Constants.R_TAGS);
} }