Пример #1
0
                  co.wrap(function *(repo, remoteName, localCommitId, target) {

    const remoteBranch = yield GitUtil.findRemoteBranch(repo,
                                                        remoteName,
                                                        target);

    // If no remote branch, we are not up-to-date.

    if (!remoteBranch) {
        return false;
    }

    const remoteCommitId = remoteBranch.target();

    if (localCommitId.equal(remoteCommitId)) {
        return true;                                                  // RETURN
    }

    // See if we would be able to fast-forward the meta-repo.  We can
    // fast-forward if the head of the remote branch is an ancestor of the head
    // of the local branch.

    const canFF =
         yield NodeGit.Graph.descendantOf(repo, localCommitId, remoteCommitId);

    if (!canFF) {
        console.error(colors.red(
                     "Cannot fast-forward the meta-repository; please pull."));
        process.exit(-1);
    }

    return false;
});
Пример #2
0
exports.isUpToDate = co.wrap(function *(repo, source, target) {
    assert.instanceOf(repo, NodeGit.Repository);
    assert.isString(source);
    assert.isString(target);
    if (source === target) {
        return true;                                                  // RETURN
    }
    return yield NodeGit.Graph.descendantOf(repo, source, target);
});
Пример #3
0
exports.merge = co.wrap(function *(repo,
                                   commit,
                                   mode,
                                   commitMessage,
                                   editMessage) {
    assert.instanceOf(repo, NodeGit.Repository);
    assert.isNumber(mode);
    assert.instanceOf(commit, NodeGit.Commit);
    if (null !== commitMessage) {
        assert.isString(commitMessage);
    }
    assert.isFunction(editMessage);

    const head = yield repo.getHeadCommit();
    const baseCommit = yield GitUtil.getMergeBase(repo, commit, head);

    if (null === baseCommit) {
        throw new UserError(`\
No commits in common with \
${colors.red(GitUtil.shortSha(commit.id().tostrS()))}`);
    }

    yield CherryPickUtil.ensureNoURLChanges(repo, commit, baseCommit);

    const result = {
        metaCommit: null,
        submoduleCommits: {},
        errorMessage: null,
    };

    const status = yield StatusUtil.getRepoStatus(repo);
    StatusUtil.ensureReady(status);
    if (!status.isDeepClean(false)) {
        // TODO: Git will refuse to run if there are staged changes, but will
        // attempt a merge if there are just workdir changes.  We should
        // support this in the future, but it basically requires us to dry-run
        // the merges in all the submodules.

        throw new UserError(`\
The repository has uncommitted changes.  Please stash or commit them before
running merge.`);
    }

    const commitSha = commit.id().tostrS();

    if (head.id().tostrS() === commit.id().tostrS()) {
        console.log("Nothing to do.");
        return result;
    }

    const upToDate  = yield NodeGit.Graph.descendantOf(repo,
                                                       head.id().tostrS(),
                                                       commitSha);

    if (upToDate) {
        console.log("Up-to-date.");
        return result;
    }

    const canFF = yield NodeGit.Graph.descendantOf(repo,
                                                   commitSha,
                                                   head.id().tostrS());
    let message = "";
    if (!canFF || MODE.FORCE_COMMIT === mode) {
        if (null === commitMessage) {
            const raw = yield editMessage();
            message = GitUtil.stripMessage(raw);
            if ("" === message) {
                console.log("Empty commit message.");
                return result;
            }
        }
        else {
            message = commitMessage;
        }
    }

    if (MODE.FF_ONLY === mode && !canFF) {
        throw new UserError(`The meta-repository cannot be fast-forwarded to \
${colors.red(commitSha)}.`);
    }
    else if (canFF) {
        console.log(`Fast-forwarding meta-repo to ${colors.green(commitSha)}.`);


        const sha = yield exports.fastForwardMerge(repo,
                                                   mode,
                                                   commit,
                                                   message);
        result.metaCommit = sha;
        return result;
    }

    const sig = repo.defaultSignature();

    const changeIndex = yield NodeGit.Merge.commits(repo, head, commit, []);
    const changes =
        yield CherryPickUtil.computeChanges(repo, changeIndex, commit);
    const index = yield repo.index();
    const opener = new Open.Opener(repo, null);

    // Perform simple changes that don't require picks -- addition, deletions,
    // and fast-forwards.

    yield CherryPickUtil.changeSubmodules(repo,
                                          opener,
                                          index,
                                          changes.simpleChanges);

    // Render any conflicts

    let errorMessage =
           yield CherryPickUtil.writeConflicts(repo, index, changes.conflicts);

    // Then do the submodule merges

    const merges =
          yield mergeSubmodules(repo, opener, index, changes.changes, message);
    result.submoduleCommits = merges.commits;
    const conflicts = merges.conflicts;

    yield CherryPickUtil.closeSubs(opener, merges);

    Object.keys(conflicts).sort().forEach(name => {
        errorMessage += SubmoduleRebaseUtil.subConflictErrorMessage(name);
    });

    // We must write the index here or the staging we've done earlier will go
    // away.
    yield SparseCheckoutUtil.writeMetaIndex(repo, index);

    if ("" !== errorMessage) {
        // We're about to fail due to conflict.  First, record that there is a
        // merge in progress so that we can continue or abort it later.
        // TODO: some day when we make use of it, write the ref name for HEAD

        const seq = new SequencerState({
            type: MERGE,
            originalHead: new CommitAndRef(head.id().tostrS(), null),
            target: new CommitAndRef(commit.id().tostrS(), null),
            currentCommit: 0,
            commits: [commit.id().tostrS()],
            message: message,
        });
        yield SequencerStateUtil.writeSequencerState(repo.path(), seq);
        result.errorMessage = errorMessage;
    } else {

        console.log(`Merging meta-repo commit ${colors.green(commitSha)}.`);

        const id = yield index.writeTreeTo(repo);

        // And finally, commit it.

        const metaCommit = yield repo.createCommit("HEAD",
                                                   sig,
                                                   sig,
                                                   message,
                                                   id,
                                                   [head, commit]);
        result.metaCommit = metaCommit.tostrS();
    }
    return result;
});
Пример #4
0
    const mergeSubmodule = co.wrap(function *(name) {
        const subRepo = yield opener.getSubrepo(name);
        const change = subs[name];

        const fromSha = change.newSha;
        yield fetcher.fetchSha(subRepo, name, fromSha);
        const subHead = yield subRepo.getHeadCommit();
        const headSha = subHead.id().tostrS();
        const fromCommit = yield subRepo.getCommit(fromSha);

        // See if up-to-date

        if (yield NodeGit.Graph.descendantOf(subRepo, headSha, fromSha)) {
            return;                                                   // RETURN
        }

        // See if can fast-forward

        if (yield NodeGit.Graph.descendantOf(subRepo, fromSha, headSha)) {
            yield GitUtil.setHeadHard(subRepo, fromCommit);
            yield index.addByPath(name);
            return result;                                            // RETURN
        }

        console.log(`Submodule ${colors.blue(name)}: merging commit \
${colors.green(fromSha)}.`);

        // Start the merge.

        let subIndex = yield NodeGit.Merge.commits(subRepo,
                                                   subHead,
                                                   fromCommit,
                                                   null);

        yield NodeGit.Checkout.index(subRepo, subIndex, {
            checkoutStrategy: NodeGit.Checkout.STRATEGY.FORCE,
        });

        // Abort if conflicted.

        if (subIndex.hasConflicts()) {
            const seq = new SequencerState({
                type: MERGE,
                originalHead: new CommitAndRef(subHead.id().tostrS(), null),
                target: new CommitAndRef(fromSha, null),
                currentCommit: 0,
                commits: [fromSha],
                message: message,
            });
            yield SequencerStateUtil.writeSequencerState(subRepo.path(), seq);
            result.conflicts[name] = fromSha;
            return;                                                   // RETURN
        }

        // Otherwise, finish off the merge.

        subIndex = yield subRepo.index();
        const treeId = yield subIndex.writeTreeTo(subRepo);
        const mergeCommit = yield subRepo.createCommit("HEAD",
                                                       sig,
                                                       sig,
                                                       message,
                                                       treeId,
                                                       [subHead, fromCommit]);
        result.commits[name] = mergeCommit.tostrS();

        // Clean up the conflict for this submodule and stage our change.

        yield index.addByPath(name);
        yield index.conflictRemove(name);
    });
Пример #5
0
 .then(([repo, local, upstream]) => {
   return Git.Graph.aheadBehind(repo, local.target(), upstream.target())
 })
Пример #6
0
exports.merge = co.wrap(function *(metaRepo,
                                   metaRepoStatus,
                                   commit,
                                   mode,
                                   commitMessage) {
    assert.instanceOf(metaRepo, NodeGit.Repository);
    assert.instanceOf(metaRepoStatus, RepoStatus);
    assert.isNumber(mode);
    assert.instanceOf(commit, NodeGit.Commit);
    assert.isString(commitMessage);

    // TODO: See how we do with a variety of edge cases, e.g.: submodules added
    // and removed.
    // TODO: Deal with conflicts.

    // Basic algorithm:
    // - start merge on meta-repo
    // - detect changes in sub-repos
    // - merge changes in sub-repos
    // - if any conflicts in sub-repos, bail
    // - finalize commit in meta-repo
    //
    // The actual problem is complicated by a couple of things:
    //
    // - oddities with and/or poor support of submodules
    // - unlike rebase and cherry-pick, which seem similar on the surface, the
    //   merge operation doesn't operate directly on the current HEAD, index,
    //   or working directory: it creates a weird virtual index
    //
    // I haven't created issues for nodegit or libgit2 yet as I'm not sure how
    // many of these problems are real problems or "by design".  If this
    // project moves out of the prototype phase, we should resolve these
    // issues as much of the code below feels like a hackish workaround.
    //
    // details to follow:

    // If the target commit is an ancestor of the derived commit, then we have
    // nothing to do; the target commit is already part of the current history.

    const commitSha = commit.id().tostrS();

    if (yield GitUtil.isUpToDate(metaRepo,
                                 metaRepoStatus.headCommit,
                                 commitSha)) {
        return null;
    }

    let canFF = yield NodeGit.Graph.descendantOf(metaRepo,
                                                 commitSha,
                                                 metaRepoStatus.headCommit);

    if (MODE.FF_ONLY === mode && !canFF) {
        throw new UserError(`The meta-repositor cannot be fast-forwarded to \
${colors.red(commitSha)}.`);
    }

    const sig = metaRepo.defaultSignature();

    // Kick off the merge.  It is important to note is that `Merge.commit` does
    // not directly modify the working directory or index.  The `metaIndex`
    // object it returns is magical, virtual, does not operate on HEAD or
    // anything, has no effect.

    const head = yield metaRepo.getCommit(metaRepoStatus.headCommit);
    const metaIndex = yield NodeGit.Merge.commits(metaRepo,
                                                  head,
                                                  commit,
                                                  null);

    let errorMessage = "";

    // `toAdd` will contain a list of paths that need to be added to the final
    // index when it's ready.  Adding them to the "virtual", `metaIndex` object
    // turns out to have no effect.  This complication is caused by a a
    // combination of merge/index weirdness and submodule weirdness.

    const toAdd = [];

    const subCommits = {};  // Record of merge commits in submodules.

    const subs = metaRepoStatus.submodules;

    const mergeEntry = co.wrap(function *(entry) {
        const path = entry.path;
        const stage = RepoStatus.getStage(entry.flags);

        // If the entry is not on the "other" side of the merge move on.

        if (RepoStatus.STAGE.THEIRS !== stage &&
            RepoStatus.STAGE.NORMAL !== stage) {
            return;                                                   // RETURN
        }

        // If it's not a submodule move on.

        if (!(path in subs)) {
            return;                                                   // RETURN
        }

        // Otherwise, we have a submodule that needs to be merged.

        const subCommitId = NodeGit.Oid.fromString(entry.id.tostrS());
        const sub = subs[path];
        const subHeadSha = sub.commitSha;
        const subCommitSha = subCommitId.tostrS();

        // Exit early without opening if we have the same commit as the one
        // we're supposed to merge to.

        if (subCommitSha === subHeadSha) {
            return;                                                   // RETURN
        }

        let subRepoStatus = sub.repoStatus;
        let subRepo;
        if (null === subRepoStatus) {
            // If this submodule's not open, open it.

            console.log(`Opening ${colors.blue(path)}.`);
            subRepo = yield Open.open(metaRepo, path, sub.indexUrl);
            subRepoStatus = yield Status.getRepoStatus(subRepo);
        }
        else {
            subRepo = yield SubmoduleUtil.getRepo(metaRepo, path);
        }
        const subCommit = yield subRepo.getCommit(subCommitId);

        // If this submodule is up-to-date with the merge commit, exit.

        if (yield GitUtil.isUpToDate(subRepo, subHeadSha, subCommitSha)) {
            console.log(`Submodule ${colors.blue(path)} is up-to-date with \
commit ${colors.green(subCommitSha)}.`);
            return;                                                   // RETURN
        }

        // If we can fast-forward, we don't need to do a merge.

        const canSubFF = yield NodeGit.Graph.descendantOf(subRepo,
                                                          subCommitSha,
                                                          subHeadSha);
        if (canSubFF && MODE.FORCE_COMMIT !== mode) {
            console.log(`Submodule ${colors.blue(path)}: fast-forward to
${colors.green(subCommitSha)}.`);
            yield NodeGit.Reset.reset(subRepo,
                                      subCommit,
                                      NodeGit.Reset.TYPE.HARD);

            // We still need to add this submodule's name to the list to add so
            // that it will be recorded to the index if the meta-repo ends up
            // generating a commit.

            toAdd.push(path);
            return;                                                   // RETURN
        }
        else if (MODE.FF_ONLY === mode) {
            // If non-ff merge is disallowed, bail.
            errorMessage += `Submodule ${colors.red(path)} could not be \
fast-forwarded.\n`;
            return;                                                   // RETURN
        }

        // We're going to generate a commit.  Note that the meta-repo cannot be
        // fast-forwarded.

        canFF = false;

        console.log(`Submodule ${colors.blue(path)}: merging commit \
${colors.green(subCommitSha)}.\n`);

        // Start the merge.

        const subHead = yield subRepo.getCommit(subHeadSha);
        let index = yield NodeGit.Merge.commits(subRepo,
                                                  subHead,
                                                  subCommit,
                                                  null);

        // Abort if conflicted.

        if (index.hasConflicts()) {
            errorMessage += `Submodule ${colors.red(path)} is conflicted.\n`;
            return;                                                   // RETURN
        }

        // Otherwise, finish off the merge.

        yield index.writeTreeTo(subRepo);
        yield NodeGit.Checkout.index(subRepo, index, {
            checkoutStrategy: NodeGit.Checkout.STRATEGY.FORCE,
        });
        index = yield subRepo.index();
        const treeId = yield index.writeTreeTo(subRepo);
        const mergeCommit = yield subRepo.createCommit("HEAD",
                                                       sig,
                                                       sig,
                                                       commitMessage,
                                                       treeId,
                                                       [subHead, subCommit]);
        subCommits[path] = mergeCommit.tostrS();

        // And add this sub-repo to the list of sub-repos that need to be added
        // to the index later.

        toAdd.push(path);
    });

    // Createa a submodule merger for each submodule in the index.

    const entries = metaIndex.entries();
    yield entries.map(mergeEntry);

    // If one of the submodules could not be merged, exit.

    if ("" !== errorMessage) {
        throw new UserError(errorMessage);
    }

    // If we've made it through the submodules and can still fast-forward, just
    // reset the head to the right commit and return.

    if (canFF && MODE.FORCE_COMMIT !== mode) {
        console.log(
            `Fast-forwarding meta-repo to ${colors.green(commitSha)}.`);
        yield NodeGit.Reset.reset(metaRepo, commit, NodeGit.Reset.TYPE.HARD);
        return {
            metaCommit: commitSha,
            submoduleCommits: subCommits,
        };
    }

    console.log(`Merging meta-repo commit ${colors.green(commitSha)}.`);

    // This bit gets a little nasty.  First, we need to put `metaIndex` into a
    // proper state and write it out.

    yield metaIndex.conflictCleanup();
    yield metaIndex.writeTreeTo(metaRepo);

    // Having committed the index with changes, we need to check it out so that
    // it's applied to the current index and working directory.  Only there
    // will we be able to properly reflect the changes to the submodules.  We
    // need to get to a point where we have a "real" index to work with.

    const checkoutOpts =  {
        checkoutStrategy: NodeGit.Checkout.STRATEGY.FORCE
    };
    yield NodeGit.Checkout.index(metaRepo, metaIndex, checkoutOpts);

    // Now that the changes are applied to the current working directory and
    // index, we can open the current index and work with it.

    const newIndex = yield metaRepo.index();

    // We've made changes to (merges into) some of the submodules; now we can
    // finally stage them into the index.

    yield toAdd.map(subName => newIndex.addByPath(subName));

    // And write that index out.

    yield newIndex.write();
    const id = yield newIndex.writeTreeTo(metaRepo);

    // And finally, commit it.

    const metaCommit = yield metaRepo.createCommit("HEAD",
                                                   sig,
                                                   sig,
                                                   commitMessage,
                                                   id,
                                                   [head, commit]);

    return {
        metaCommit: metaCommit.tostrS(),
        submoduleCommits: subCommits,
    };
});
Пример #7
0
    const mergeEntry = co.wrap(function *(entry) {
        const path = entry.path;
        const stage = RepoStatus.getStage(entry.flags);

        // If the entry is not on the "other" side of the merge move on.

        if (RepoStatus.STAGE.THEIRS !== stage &&
            RepoStatus.STAGE.NORMAL !== stage) {
            return;                                                   // RETURN
        }

        // If it's not a submodule move on.

        if (!(path in subs)) {
            return;                                                   // RETURN
        }

        // Otherwise, we have a submodule that needs to be merged.

        const subCommitId = NodeGit.Oid.fromString(entry.id.tostrS());
        const sub = subs[path];
        const subHeadSha = sub.commitSha;
        const subCommitSha = subCommitId.tostrS();

        // Exit early without opening if we have the same commit as the one
        // we're supposed to merge to.

        if (subCommitSha === subHeadSha) {
            return;                                                   // RETURN
        }

        let subRepoStatus = sub.repoStatus;
        let subRepo;
        if (null === subRepoStatus) {
            // If this submodule's not open, open it.

            console.log(`Opening ${colors.blue(path)}.`);
            subRepo = yield Open.open(metaRepo, path, sub.indexUrl);
            subRepoStatus = yield Status.getRepoStatus(subRepo);
        }
        else {
            subRepo = yield SubmoduleUtil.getRepo(metaRepo, path);
        }
        const subCommit = yield subRepo.getCommit(subCommitId);

        // If this submodule is up-to-date with the merge commit, exit.

        if (yield GitUtil.isUpToDate(subRepo, subHeadSha, subCommitSha)) {
            console.log(`Submodule ${colors.blue(path)} is up-to-date with \
commit ${colors.green(subCommitSha)}.`);
            return;                                                   // RETURN
        }

        // If we can fast-forward, we don't need to do a merge.

        const canSubFF = yield NodeGit.Graph.descendantOf(subRepo,
                                                          subCommitSha,
                                                          subHeadSha);
        if (canSubFF && MODE.FORCE_COMMIT !== mode) {
            console.log(`Submodule ${colors.blue(path)}: fast-forward to
${colors.green(subCommitSha)}.`);
            yield NodeGit.Reset.reset(subRepo,
                                      subCommit,
                                      NodeGit.Reset.TYPE.HARD);

            // We still need to add this submodule's name to the list to add so
            // that it will be recorded to the index if the meta-repo ends up
            // generating a commit.

            toAdd.push(path);
            return;                                                   // RETURN
        }
        else if (MODE.FF_ONLY === mode) {
            // If non-ff merge is disallowed, bail.
            errorMessage += `Submodule ${colors.red(path)} could not be \
fast-forwarded.\n`;
            return;                                                   // RETURN
        }

        // We're going to generate a commit.  Note that the meta-repo cannot be
        // fast-forwarded.

        canFF = false;

        console.log(`Submodule ${colors.blue(path)}: merging commit \
${colors.green(subCommitSha)}.\n`);

        // Start the merge.

        const subHead = yield subRepo.getCommit(subHeadSha);
        let index = yield NodeGit.Merge.commits(subRepo,
                                                  subHead,
                                                  subCommit,
                                                  null);

        // Abort if conflicted.

        if (index.hasConflicts()) {
            errorMessage += `Submodule ${colors.red(path)} is conflicted.\n`;
            return;                                                   // RETURN
        }

        // Otherwise, finish off the merge.

        yield index.writeTreeTo(subRepo);
        yield NodeGit.Checkout.index(subRepo, index, {
            checkoutStrategy: NodeGit.Checkout.STRATEGY.FORCE,
        });
        index = yield subRepo.index();
        const treeId = yield index.writeTreeTo(subRepo);
        const mergeCommit = yield subRepo.createCommit("HEAD",
                                                       sig,
                                                       sig,
                                                       commitMessage,
                                                       treeId,
                                                       [subHead, subCommit]);
        subCommits[path] = mergeCommit.tostrS();

        // And add this sub-repo to the list of sub-repos that need to be added
        // to the index later.

        toAdd.push(path);
    });