/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.zeppelin.notebook.repo;
import java.io.File;
import java.io.IOException;
import java.util.List;
import org.apache.zeppelin.conf.ZeppelinConfiguration;
import org.apache.zeppelin.notebook.Note;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.diff.DiffEntry;
import org.eclipse.jgit.dircache.DirCache;
import org.eclipse.jgit.internal.storage.file.FileRepository;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
/**
* NotebookRepo that hosts all the notebook FS in a single Git repo
*
* This impl intended to be simple and straightforward:
* - does not handle branches
* - only basic local git file repo, no remote Github push\pull yet
*
* TODO(bzz): add default .gitignore
*/
public class GitNotebookRepo extends VFSNotebookRepo implements NotebookRepoVersioned {
private static final Logger LOG = LoggerFactory.getLogger(GitNotebookRepo.class);
private String localPath;
private Git git;
public GitNotebookRepo(ZeppelinConfiguration conf) throws IOException {
super(conf);
localPath = getRootDir().getName().getPath();
LOG.info("Opening a git repo at '{}'", localPath);
Repository localRepo = new FileRepository(Joiner.on(File.separator).join(localPath, ".git"));
if (!localRepo.getDirectory().exists()) {
LOG.info("Git repo {} does not exist, creating a new one", localRepo.getDirectory());
localRepo.create();
}
git = new Git(localRepo);
maybeAddAndCommit(".");
}
@Override
public synchronized void save(Note note) throws IOException {
super.save(note);
maybeAddAndCommit(note.getId());
}
private void maybeAddAndCommit(String pattern) {
try {
List<DiffEntry> gitDiff = git.diff().call();
if (!gitDiff.isEmpty()) {
LOG.debug("Changes found for pattern '{}': {}", pattern, gitDiff);
DirCache added = git.add().addFilepattern(pattern).call();
LOG.debug("{} changes are about to be commited", added.getEntryCount());
git.commit().setMessage("Updated " + pattern).call();
} else {
LOG.debug("No changes found {}", pattern);
}
} catch (GitAPIException e) {
LOG.error("Faild to add+comit {} to Git", pattern, e);
}
}
@Override
public Note get(String noteId, String rev) throws IOException {
//TODO(bzz): something like 'git checkout rev', that will not change-the-world though
return super.get(noteId);
}
@Override
public List<Rev> history(String noteId) {
List<Rev> history = Lists.newArrayList();
LOG.debug("Listing history for {}:", noteId);
try {
Iterable<RevCommit> logs = git.log().addPath(noteId).call();
for (RevCommit log: logs) {
history.add(new Rev(log.getName(), log.getCommitTime()));
LOG.debug(" - ({},{})", log.getName(), log.getCommitTime());
}
} catch (GitAPIException e) {
LOG.error("Failed to get logs for {}", noteId, e);
}
return history;
}
@Override
public void close() {
git.getRepository().close();
}
//DI replacements for Tests
Git getGit() {
return git;
}
void setGit(Git git) {
this.git = git;
}
}