Notebook: save multiple checkpoint at the same

Created on 25 Apr 2018  路  9Comments  路  Source: jupyter/notebook

hi~, I don't know what happend with my notebook yeasterday, I find that some import code of my one notebook is missing this morning, and the checkpont is the same~
So why don't have multipe checkpoint at every saving time ?
So I modidy the code: notebook/services/contents/fileio.py
qq 20180425131946

It woks~
qq 20180425132103

that will be added to future version of notebook?

Thanks~
Si

Most helpful comment

I will really appreciate a feature like this since as far as I know the undo functionality is limited to undo deleted cells.
BTW, a VCS usually does not replace a undo tree.

All 9 comments

The checkpoint is updated whenever you save manually; the only difference is that the checkpoint isn't updated by autosave.

We don't want to try to support multiple checkpoints by default, because that would be reinventing version control badly.

I agreed with your opinion before a few days ago, but sometimes I deleted some cells of codes and didn't realize that and save auto or manually, So missing the lost code forever. I just want to do some things in case of missing some cell of codes~

We recommend using version control - which usually means git these days. Github has some tutorials if you're not familiar with git: https://help.github.com/ . You can also setup nbdime to make a better experience with notebooks in version control.

ok, I will try nbdime later~
Thanks~

No problem :-)

I will really appreciate a feature like this since as far as I know the undo functionality is limited to undo deleted cells.
BTW, a VCS usually does not replace a undo tree.

We recommend using version control - which usually means git these days.

Yes, but I don't want a big fancy official save of changes. I'm not going to publish this. I want a checkpoint. This is safety net, not version control.

Inspired from @orangeSi extended his code for creating local git repos in every .ipynb_checkpoints directory.
It will create commit whenever someone saves a notebook and the author of commit will be the hostname of machine that is available along with the ip.

#jupyter_git_patch.py
# jupyter-git-patch start

def copy2_safe(src, dst, log=None):
    """copy src to dst

    like shutil.copy2, but log errors in copystat instead of raising
    """
    shutil.copyfile(src, dst)
    try:
        if dst.count('{0}.ipynb_checkpoints{0}'.format(os.sep)):
            chkpoint = dst.split(__import__("os").sep)[-1]
            ipydir = dst.rstrip(chkpoint)
            r = 0
            try:
                r = __import__("git").Repo(ipydir)
            except __import__("git").exc.InvalidGitRepositoryError:
                r = __import__("git").Repo.init(ipydir)
            if [1 for d in r.index.diff(None) if chkpoint in d.a_path] or chkpoint in r.untracked_files:
                saviuser,saviuserr = "defaulter","defaulterr"
                try:
                    frameno=4#py3
                    saviuser = __import__("inspect").getouterframes(__import__("inspect").currentframe())[frameno][0].f_locals['self'].request.remote_ip
                    saviuserr = __import__("socket").gethostbyaddr(saviuser)[0]
                except Exception as e:
                    print("Attempt remote user detection: py3 failed:{} Trying py2 now.".format(e))
                    try:
                        frameno=10#py2
                        saviuser = __import__("inspect").getouterframes(__import__("inspect").currentframe())[frameno][0].f_locals['self'].request.remote_ip
                        saviuserr = __import__("socket").gethostbyaddr(saviuser)[0]
                    except Exception as e2:
                        print("Failed in remote user detection:", e2)
                r.config_writer().set_value("user", "name", saviuserr).release()
                r.config_writer().set_value("user", "email", saviuser).release()
                try:
                    r.git.add(chkpoint)
                except (__import__("git").exc.GitCommandError, IOError):
                    if sum(__import__("traceback").format_exc().count(_c) for _c in "Unable to create,index.lock,: File exists.".split(','))>2:
                        # index.lock needs to be deleted.
                        __import__("time").sleep(1)
                        to_delete=__import__("os").sep.join([ipydir, ".git", "index.lock"])
                        __import__("os").remove(to_delete)
                        print("Git commit: Deleted older existing git-locks.. Retrying to commit..[{}]".format(to_delete))
                    r.git.add(chkpoint)
                r.index.commit(chkpoint.rstrip('checkpoint.ipynb') + __import__("datetime").datetime.now().isoformat())
    except Exception as e:
        print ("Git commiting failed as:", e,'\n',__import__("traceback").format_exc())
    try:
        shutil.copystat(src, dst)
    except OSError:
        if log:
            log.debug("copystat on %s failed", dst, exc_info=True)


# jupyter-git-patch end

PS: Commit shall contain only the particular file and not the whole directory.

In Dockerfile (for those using jupyter out of docker):

#apply auto-git patch of jupyter
COPY jupyter_git_patch.py /tmp/jupyter_git_patch.py
RUN /opt/conda/bin/pip install gitpython
RUN cat /tmp/jupyter_git_patch.py >> /opt/conda/lib/python3.7/site-packages/notebook/services/contents/fileio.py

Although, this is not a recommended approach, I used in this in my project to track which user did what changes in a notebook.
To revert, one would have to go the .ipynb_checkpoints directory and git revert that particular commit.

The full code from OrangeSi:

def copy2_safe(src, dst, log=None):
"""copy src to dst

like shutil.copy2, but log errors in copystat instead of raising
"""
shutil.copyfile(src, dst)
try:
    shutil.copystat(src, dst)
except OSError:
    if log:
        log.debug("copystat on %s failed", dst, exc_info=True)
if re.search(".ipynb_checkpoints",dst):
    now= datetime.datetime.now()
    now = re.sub(':','.',now.isoformat())
    now = re.sub('-','.',now)
    dst = dst+"."+now
    shutil.copyfile(src,dst)
    try:
        shutil.copystat(src,dst)
    except OSError:
        if log:
            log.debug("copystat on %s failed",dst,exc_info=True)
Was this page helpful?
0 / 5 - 0 ratings