From e404e72fb5b542ec280af0f78305ad0a9b89045e Mon Sep 17 00:00:00 2001 From: Avery Pennarun Date: Fri, 9 Jul 2010 00:49:38 -0400 Subject: [PATCH] web: when saving changes from editing, do a proper git merge. Otherwise two people could start editing at once, and save one after the other. The second would clobber the first's changes. Now both saves will have the same parent git commit, and we'll merge them together using our standard resolve() mechanism. While we're here, let's also clean up repo.py to throw exceptions instead of using return values. The latter are very un-pythonic (and nobody was checking the return values half the time anyway, which is pretty bad). --- cmd/commit-cmd.py | 3 +- cmd/pull-cmd.py | 7 ++-- cmd/push-cmd.py | 7 ++-- cmd/resolve-cmd.py | 3 +- cmd/sync-cmd.py | 9 ++--- cmd/web-cmd.py | 8 +++- lib/bog/repo.py | 93 +++++++++++++++++++++++++++++++++++---------- templates/edit.html | 2 +- 8 files changed, 93 insertions(+), 39 deletions(-) diff --git a/cmd/commit-cmd.py b/cmd/commit-cmd.py index 9d2f8bd..38e35c3 100755 --- a/cmd/commit-cmd.py +++ b/cmd/commit-cmd.py @@ -14,4 +14,5 @@ if extra: o.fatal('no arguments expected') -sys.exit(repo.commit(msg = opt.message or 'Commit')) +repo.commit(msg = opt.message or 'Commit') + diff --git a/cmd/pull-cmd.py b/cmd/pull-cmd.py index ab32aae..9c9b433 100755 --- a/cmd/pull-cmd.py +++ b/cmd/pull-cmd.py @@ -13,11 +13,10 @@ o.fatal('no arguments expected') repo.check_dir() -rv = repo.commit('Commit (pull)') -rv += repo.resolve() +repo.commit('Commit (pull)') +repo.resolve() if repo.remote_url(): - rv += repo.pull() + repo.pull() else: log('No remote repository configured.\n') -sys.exit(rv) diff --git a/cmd/push-cmd.py b/cmd/push-cmd.py index 628ecd4..9ae1657 100755 --- a/cmd/push-cmd.py +++ b/cmd/push-cmd.py @@ -13,7 +13,6 @@ o.fatal('no arguments expected') repo.check_dir() -rv = repo.commit('Commit (push)') -rv += repo.resolve() -rv += repo.push() -sys.exit(rv) +repo.commit('Commit (push)') +repo.resolve() +repo.push() diff --git a/cmd/resolve-cmd.py b/cmd/resolve-cmd.py index f570af5..d26005a 100755 --- a/cmd/resolve-cmd.py +++ b/cmd/resolve-cmd.py @@ -14,5 +14,4 @@ repo.check_dir() repo.commit('Commit (resolve)') -sys.exit(repo.resolve()) - +repo.resolve() diff --git a/cmd/sync-cmd.py b/cmd/sync-cmd.py index 5e57425..3a6c8b4 100755 --- a/cmd/sync-cmd.py +++ b/cmd/sync-cmd.py @@ -13,8 +13,7 @@ o.fatal('no arguments expected') repo.check_dir() -rv = repo.commit('Commit (sync)') -rv += repo.resolve() -rv += repo.pull() -rv += repo.push() -sys.exit(rv) +repo.commit('Commit (sync)') +repo.resolve() +repo.pull() +repo.push() diff --git a/cmd/web-cmd.py b/cmd/web-cmd.py index 81b7054..f0f2c0c 100755 --- a/cmd/web-cmd.py +++ b/cmd/web-cmd.py @@ -149,13 +149,17 @@ def get(self): self.render('edit.html', subpage='/sched/edit', title = 'Edit Schedule', tasks = get_sched(), - text = open(mainpath).read()) + text = open(mainpath).read(), + commitid = repo.head_commitid()) def post(self): + repo.commit() t = str(self.request.body) open(mainpath, 'wb').write(t) self.write('ok') - repo.commit('Edited (web)') + + cid = repo.vcommit('Edited (web)', self.get_argument('commitid')) + repo.resolve(cid) print 'Updated schedule (%s).' % repr(t[:40]) diff --git a/lib/bog/repo.py b/lib/bog/repo.py index e0bdb7e..f2ec0b7 100644 --- a/lib/bog/repo.py +++ b/lib/bog/repo.py @@ -1,9 +1,27 @@ -import os, subprocess, glob +import os, subprocess, glob, tempfile from bog.helpers import * +def _run(argv): + rv = subprocess.call(argv) + if rv: + raise Exception('%r returned %d' % (argv, rv)) + + +def _grab(argv, stdin=[]): + p = subprocess.Popen(argv, stdout=subprocess.PIPE, stdin=subprocess.PIPE) + for i in stdin: + p.stdin.write(i) + p.stdin.close() + data = p.stdout.read().strip() + rv = p.wait() + if rv: + raise Exception('%r returned %d' % (argv, rv)) + return data + + def get_dir(): - bd = os.getenv('BOG_DIR') + bd = os.environ.get('BOG_DIR') if bd: return os.path.join(bd, '') # ensure terminating / @@ -27,38 +45,73 @@ def check_dir(): def remote_url(): bogdir = check_dir() os.environ['GIT_DIR'] = os.path.join(bogdir, '.git') - p = subprocess.Popen(['git', 'config', 'remote.origin.url'], - stdout=subprocess.PIPE) - oldurl = p.stdout.read().strip() - p.wait() + oldurl = _grab(['git', 'config', 'remote.origin.url']) return oldurl or None -def commit(msg='Checkpoint'): +def head_commitid(): bogdir = check_dir() os.environ['GIT_DIR'] = os.path.join(bogdir, '.git') - subprocess.call(['git', 'add', '-A', bogdir]) - rv = subprocess.call(['git', 'commit', '-m', msg]) - subprocess.call(['git', 'branch', 'merge-me'], - stderr=open('/dev/null', 'w')) - return rv + try: + line = _grab(['git', 'show-ref', '--head', 'HEAD']) + commit = line.split()[0] + except Exception: + commit = None + return commit or None + + +def tree_from_commit(cid): + return _grab(['git', 'rev-parse', '%s:' % cid]) + + +def vcommit(msg, parent): + bogdir = check_dir() + os.environ['GIT_DIR'] = os.path.join(bogdir, '.git') + (tmpfd,tmpname) = tempfile.mkstemp() + try: + os.environ['GIT_INDEX_FILE'] = tmpname + if not parent: + oldtid = _grab(['git', 'mktree']) # empty tree + else: + oldtid = tree_from_commit(parent) + _run(['git', 'read-tree', oldtid]) + _run(['git', 'add', '-A', bogdir]) + tid = _grab(['git', 'write-tree']) + if oldtid != tid: + cid = _grab(['git', 'commit-tree', tid] + + (parent and ['-p', parent] or []), + stdin = [msg]) + else: + cid = parent + del os.environ['GIT_INDEX_FILE'] + headcommit = head_commitid() + if headcommit: + _run(['git', 'reset', '--hard', headcommit]) + return cid + finally: + if os.environ.get('GIT_INDEX_FILE') != None: + del os.environ['GIT_INDEX_FILE'] + os.close(tmpfd) + + +def commit(msg='Checkpoint'): + cid = vcommit(msg, head_commitid()) + resolve(cid) -def resolve(): +def resolve(merge_from='merge-me'): gitdir = os.path.join(check_dir(), '.git') os.environ['GIT_DIR'] = gitdir - rv = subprocess.call(['git', 'merge', 'merge-me']) - rv += subprocess.call(['git', 'push', gitdir, 'master:merge-me']) - return rv + _run(['git', 'merge', '--', merge_from]) + _run(['git', 'push', gitdir, 'master:merge-me']) def push(): os.environ['GIT_DIR'] = os.path.join(check_dir(), '.git') - return subprocess.call(['git', 'push', 'origin', 'master:merge-me']) + _run(['git', 'push', 'origin', 'master:merge-me']) def pull(): os.environ['GIT_DIR'] = os.path.join(check_dir(), '.git') - rv = subprocess.call(['git', 'pull', 'origin', 'master']) - rv += subprocess.call(['git', 'pull', 'origin', 'merge-me']) - return rv + _run(['git', 'pull', 'origin', 'master']) + _run(['git', 'pull', 'origin', 'merge-me']) diff --git a/templates/edit.html b/templates/edit.html index 389116c..29e1901 100644 --- a/templates/edit.html +++ b/templates/edit.html @@ -69,7 +69,7 @@ sched_post = function() { var v = $('#schedtext').val(); if (v != oldtext) { - $.post('/sched/edit', v); + $.post('/sched/edit?commitid={{commitid}}', v); oldtext = v; } };