push(f) - the patch was fast-forwarded
undo - the patch boundaries were restored to the old values
-Note that only the diffs shown in the 'refresh' and 'undo' actions are
-meaningful for the patch changes. The 'push' actions represent the
-changes to the entire base of the current patch. Conflicts reset the
-patch content and a subsequent 'refresh' will show the entire patch."""
+Note that only the diffs shown in the 'refresh', 'undo' and 'sync'
+actions are meaningful for the patch changes. The 'push' actions
+represent the changes to the entire base of the current
+patch. Conflicts reset the patch content and a subsequent 'refresh'
+will show the entire patch."""
options = [make_option('-b', '--branch',
help = 'use BRANCH instead of the default one'),
descr = commit.get_log().rstrip()
if show_patch:
- if descr.startswith('refresh') or descr.startswith('undo'):
+ if descr.startswith('refresh') or descr.startswith('undo') \
+ or descr.startswith('sync'):
diff_str = '%s%s\n' % (diff_str,
git.pretty_commit(commit.get_id_hash()))
else:
--- /dev/null
+__copyright__ = """
+Copyright (C) 2006, Catalin Marinas <catalin.marinas@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License version 2 as
+published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+"""
+
+import sys, os
+from optparse import OptionParser, make_option
+
+import stgit.commands.common
+from stgit.commands.common import *
+from stgit.utils import *
+from stgit import stack, git
+
+
+help = 'synchronise patches with a branch or a series'
+usage = """%prog [options] [<patch1>] [<patch2>] [<patch3>..<patch4>]
+
+For each of the specified patches perform a three-way merge with the
+same patch in the specified branch or series. The command can be used
+for keeping patches on several branches in sync. Note that the
+operation may fail for some patches because of conflicts. The patches
+in the series must apply cleanly.
+
+The sync operation can be reverted for individual patches with --undo."""
+
+options = [make_option('-a', '--all',
+ help = 'synchronise all the patches',
+ action = 'store_true'),
+ make_option('-b', '--branch',
+ help = 'syncronise patches with BRANCH'),
+ make_option('-s', '--series',
+ help = 'syncronise patches with SERIES'),
+ make_option('--undo',
+ help = 'undo the synchronisation of the current patch',
+ action = 'store_true')]
+
+def __check_all():
+ check_local_changes()
+ check_conflicts()
+ check_head_top_equal()
+
+def __branch_merge_patch(remote_series, pname):
+ """Merge a patch from a remote branch into the current tree.
+ """
+ patch = remote_series.get_patch(pname)
+ git.merge(patch.get_bottom(), git.get_head(), patch.get_top())
+
+def __series_merge_patch(base, patchdir, pname):
+ """Merge a patch file with the given StGIT patch.
+ """
+ patchfile = os.path.join(patchdir, pname)
+ git.apply_patch(filename = patchfile, base = base)
+
+def func(parser, options, args):
+ """Synchronise a range of patches
+ """
+ global crt_series
+
+ if options.undo:
+ if options.branch or options.series:
+ raise CmdException, \
+ '--undo cannot be specified with --branch or --series'
+ __check_all()
+
+ print 'Undoing the "%s" sync...' % crt_series.get_current(),
+ sys.stdout.flush()
+
+ crt_series.undo_refresh()
+ git.reset()
+
+ print 'done'
+ return
+
+ if options.branch:
+ # the main function already made crt_series to be the remote
+ # branch
+ remote_series = crt_series
+ stgit.commands.common.crt_series = crt_series = stack.Series()
+ if options.branch == crt_series.get_branch():
+ raise CmdException, 'Cannot synchronise with the current branch'
+ remote_patches = remote_series.get_applied()
+
+ # the merge function merge_patch(patch, pname)
+ merge_patch = lambda patch, pname: \
+ __branch_merge_patch(remote_series, pname)
+ elif options.series:
+ patchdir = os.path.dirname(options.series)
+
+ remote_patches = []
+ f = file(options.series)
+ for line in f:
+ p = re.sub('#.*$', '', line).strip()
+ if not p:
+ continue
+ remote_patches.append(p)
+ f.close()
+
+ # the merge function merge_patch(patch, pname)
+ merge_patch = lambda patch, pname: \
+ __series_merge_patch(patch.get_bottom(), patchdir, pname)
+ else:
+ raise CmdException, 'No remote branch or series specified'
+
+ applied = crt_series.get_applied()
+
+ if options.all:
+ patches = applied
+ elif len(args) != 0:
+ patches = parse_patches(args, applied, ordered = True)
+ else:
+ parser.error('no patches specified')
+
+ if not patches:
+ raise CmdException, 'No patches to synchronise'
+
+ __check_all()
+
+ # only keep the patches to be synchronised
+ sync_patches = [p for p in patches if p in remote_patches]
+ if not sync_patches:
+ raise CmdException, 'No common patches to be synchronised'
+
+ # pop to the one before the first patch to be synchronised
+ popped = applied[applied.index(sync_patches[0]) + 1:]
+ if popped:
+ pop_patches(popped[::-1])
+
+ for p in sync_patches:
+ if p in popped:
+ # push to this patch
+ idx = popped.index(p) + 1
+ push_patches(popped[:idx])
+ del popped[:idx]
+
+ # the actual sync
+ print 'Synchronising "%s"...' % p,
+ sys.stdout.flush()
+
+ patch = crt_series.get_patch(p)
+ bottom = patch.get_bottom()
+ top = patch.get_top()
+
+ # reset the patch backup information. That's needed in case we
+ # undo the sync but there were no changes made
+ patch.set_bottom(bottom, backup = True)
+ patch.set_top(top, backup = True)
+
+ # the actual merging (either from a branch or an external file)
+ merge_patch(patch, p)
+
+ if git.local_changes(verbose = False):
+ # index (cache) already updated by the git merge. The
+ # backup information was already reset above
+ crt_series.refresh_patch(cache_update = False, backup = False,
+ log = 'sync')
+ print 'done (updated)'
+ else:
+ print 'done'
+
+ # push the remaining patches
+ if popped:
+ push_patches(popped)
--- /dev/null
+#!/bin/sh
+#
+# Copyright (c) 2006 Catalin Marinas
+#
+
+test_description='Test the sync command.'
+
+. ./test-lib.sh
+
+test_expect_success \
+ 'Initialize the StGIT repository' \
+ '
+ stg init
+ '
+
+test_expect_success \
+ 'Create some patches' \
+ '
+ stg new p1 -m p1 &&
+ echo foo1 > foo1.txt &&
+ stg add foo1.txt &&
+ stg refresh &&
+ stg new p2 -m p2 &&
+ echo foo2 > foo2.txt &&
+ stg add foo2.txt &&
+ stg refresh &&
+ stg new p3 -m p3 &&
+ echo foo3 > foo3.txt &&
+ stg add foo3.txt &&
+ stg refresh &&
+ stg export &&
+ stg pop
+ '
+
+test_expect_success \
+ 'Create a branch with empty patches' \
+ '
+ stg branch -c foo base &&
+ stg new p1 -m p1 &&
+ stg new p2 -m p2 &&
+ stg new p3 -m p3
+ test $(stg applied -c) -eq 3
+ '
+
+test_expect_success \
+ 'Synchronise second patch with the master branch' \
+ '
+ stg sync -b master p2 &&
+ test $(stg applied -c) -eq 3 &&
+ test $(cat foo2.txt) == "foo2"
+ '
+
+test_expect_success \
+ 'Synchronise the first two patches with the master branch' \
+ '
+ stg sync -b master -a &&
+ test $(stg applied -c) -eq 3 &&
+ test $(cat foo1.txt) == "foo1" &&
+ test $(cat foo2.txt) == "foo2"
+ '
+
+test_expect_success \
+ 'Synchronise all the patches with the exported series' \
+ '
+ stg sync -s patches-master/series -a &&
+ test $(stg applied -c) -eq 3 &&
+ test $(cat foo1.txt) == "foo1" &&
+ test $(cat foo2.txt) == "foo2" &&
+ test $(cat foo3.txt) == "foo3"
+ '
+
+test_expect_success \
+ 'Modify the master patches' \
+ '
+ stg branch master &&
+ stg goto p1 &&
+ echo bar1 >> foo1.txt &&
+ stg refresh &&
+ stg goto p2 &&
+ echo bar2 > bar2.txt &&
+ stg add bar2.txt &&
+ stg refresh &&
+ stg goto p3 &&
+ echo bar3 >> foo3.txt &&
+ stg refresh &&
+ stg export &&
+ stg branch foo
+ '
+
+test_expect_success \
+ 'Synchronise second patch with the master branch' \
+ '
+ stg sync -b master p2 &&
+ test $(stg applied -c) -eq 3 &&
+ test $(cat bar2.txt) == "bar2"
+ '
+
+test_expect_failure \
+ 'Synchronise the first two patches with the master branch (to fail)' \
+ '
+ stg sync -b master -a
+ '
+
+test_expect_success \
+ 'Restore the stack status after the failed sync' \
+ '
+ test $(stg applied -c) -eq 1 &&
+ stg resolved -a &&
+ stg refresh &&
+ stg goto p3
+ '
+
+test_expect_failure \
+ 'Synchronise the third patch with the exported series (to fail)' \
+ '
+ stg sync -s patches-master/series p3
+ '
+
+test_expect_success \
+ 'Restore the stack status after the failed sync' \
+ '
+ test $(stg applied -c) -eq 3 &&
+ stg resolved -a &&
+ stg refresh
+ '
+
+test_done