Some of these patches were posted before and there were some suggestions
which I added. The "Check for local changes with goto" patch was
slightly improved so that the "stgit.keep" option could be set to "yes"
if this is preferred as a default behaviour.
I plan to implement transition as many commands as possible to the new
infrastructure. For some of them it should be trivial. The "status"
command needs some support in stgit.lib.
Catalin Marinas (5):
Convert "float" to the lib infrastructure
Convert "sink" to the new infrastructure
Add automatic git-mergetool invocation to the new infrastructure
Add mergetool support to the classic StGit infrastructure
Check for local changes with "goto"
examples/gitconfig | 24 +------
stgit/argparse.py | 5 +
stgit/commands/edit.py | 2 -
stgit/commands/float.py | 75 +++++++++-------------
stgit/commands/goto.py | 9 ++-
stgit/commands/resolved.py | 5 -
stgit/commands/sink.py | 86 +++++++++++--------------
stgit/config.py | 1
stgit/git.py | 33 ++++++----
stgit/gitmergeonefile.py | 150 --------------------------------------------
stgit/lib/git.py | 35 +++++++++-
stgit/lib/transaction.py | 17 ++++-
t/t0002-status.sh | 3 -
t/t1501-sink.sh | 2 -
t/t2300-refresh-subdir.sh | 2 -
t/t2800-goto-subdir.sh | 4 +
t/t3000-dirty-merge.sh | 2 -
17 files changed, 153 insertions(+), 302 deletions(-)
delete mode 100644 stgit/gitmergeonefile.py
--
Catalin
Since Git already has a tool for interactively solving conflicts which
is highly customisable, there is no need to duplicate this feature via
the i3merge and i2merge configuration options. The user-visible change
is that now mergetool is invoked rather than the previously customised
interactive merging tool.
The stgit.keeporig option is no longer available to be more consistent
with the Git behaviour.
Signed-off-by: Catalin Marinas <redacted>
---
examples/gitconfig | 21 +-----
stgit/commands/resolved.py | 5 -
stgit/config.py | 1
stgit/git.py | 33 ++++++----
stgit/gitmergeonefile.py | 150 --------------------------------------------
t/t0002-status.sh | 3 -
6 files changed, 22 insertions(+), 191 deletions(-)
delete mode 100644 stgit/gitmergeonefile.py
@@ -22,7 +22,6 @@ from stgit.commands.common import *fromstgit.utilsimport*fromstgitimportargparse,stack,git,basedirfromstgit.configimportconfig,file_extensions-fromstgit.gitmergeonefileimportinteractive_mergehelp='Mark a file conflict as solved'kind='wc'
@@ -18,7 +18,7 @@ along with this program; if not, write to the Free SoftwareFoundation,Inc.,59TemplePlace,Suite330,Boston,MA02111-1307USA"""-importsys,os,re,gitmergeonefile+importsys,os,refromshutilimportcopyfilefromstgit.exceptionimport*
@@ -632,19 +632,25 @@ def merge_recursive(base, head1, head2):output=p.output_lines()ifp.exitcode:# There were conflicts-conflicts=[l.strip()forlinoutputifl.startswith('CONFLICT')]+ifconfig.get('stgit.autoimerge')=='yes':+mergetool()+else:+conflicts=[lforlinoutputifl.startswith('CONFLICT')]+out.info(*conflicts)+raiseGitException,"%d conflict(s)"%len(conflicts)++defmergetool(files=()):+"""Invoke 'git mergetool' to resolve any outstanding conflicts. If 'not+files', all the files in an unmerged state will be processed."""+err=os.system('git mergetool %s'%' '.join(files))+# check for unmerged entries (prepend 'CONFLICT ' for consistency with+# merge_recursive())+conflicts=['CONFLICT '+fforfinget_conflicts()]+ifconflicts:out.info(*conflicts)--# try the interactive merge or stage checkout (if enabled)-forfilenameinget_conflicts():-if(gitmergeonefile.merge(filename)):-# interactive merge succeeded-resolved([filename])--# any conflicts left unsolved?-cn=len(get_conflicts())-ifcn:-raiseGitException,"%d conflict(s)"%cn+raiseGitException,"%d conflict(s)"%len(conflicts)+eliferr:+raiseGitException('"git mergetool" failed, exit code: %d'%err)defdiff(files=None,rev1='HEAD',rev2=None,diff_flags=[],binary=True):
@@ -754,7 +760,6 @@ def resolved(filenames, reset = None):'--stdin','-z').input_nulterm(filenames).no_output()GRun('update-index','--add','--').xargs(filenames)forfilenameinfilenames:-gitmergeonefile.clean_up(filename)# update the access and modificatied timesos.utime(filename,None)
@@ -1,150 +0,0 @@-"""Performs a 3-way merge for GIT files-"""--__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 stgit.exception import *-from stgit import basedir-from stgit.config import config, file_extensions, ConfigOption-from stgit.utils import append_string-from stgit.out import *-from stgit.run import *--class GitMergeException(StgException):- pass---#-# Options-#-autoimerge = ConfigOption('stgit', 'autoimerge')-keeporig = ConfigOption('stgit', 'keeporig')--#-# Utility functions-#-def __str2none(x):- if x == '':- return None- else:- return x--class MRun(Run):- exc = GitMergeException # use a custom exception class on errors--def __checkout_stages(filename):- """Check-out the merge stages in the index for the give file- """- extensions = file_extensions()- line = MRun('git', 'checkout-index', '--stage=all', '--', filename- ).output_one_line()- stages, path = line.split('\t')- stages = dict(zip(['ancestor', 'current', 'patched'],- stages.split(' ')))-- for stage, fn in stages.iteritems():- if stages[stage] == '.':- stages[stage] = None- else:- newname = filename + extensions[stage]- if os.path.exists(newname):- # remove the stage if it is already checked out- os.remove(newname)- os.rename(stages[stage], newname)- stages[stage] = newname-- return stages--def __remove_stages(filename):- """Remove the merge stages from the working directory- """- extensions = file_extensions()- for ext in extensions.itervalues():- fn = filename + ext- if os.path.isfile(fn):- os.remove(fn)--def interactive_merge(filename):- """Run the interactive merger on the given file. Stages will be- removed according to stgit.keeporig. If successful and stages- kept, they will be removed via git.resolved().- """- stages = __checkout_stages(filename)-- try:- # Check whether we have all the files for the merge.- if not (stages['current'] and stages['patched']):- raise GitMergeException('Cannot run the interactive merge')-- if stages['ancestor']:- three_way = True- files_dict = {'branch1': stages['current'],- 'ancestor': stages['ancestor'],- 'branch2': stages['patched'],- 'output': filename}- imerger = config.get('stgit.i3merge')- else:- three_way = False- files_dict = {'branch1': stages['current'],- 'branch2': stages['patched'],- 'output': filename}- imerger = config.get('stgit.i2merge')-- if not imerger:- raise GitMergeException, 'No interactive merge command configured'-- mtime = os.path.getmtime(filename)-- out.start('Trying the interactive %s merge'- % (three_way and 'three-way' or 'two-way'))- err = os.system(imerger % files_dict)- out.done()- if err != 0:- raise GitMergeException, 'The interactive merge failed'- if not os.path.isfile(filename):- raise GitMergeException, 'The "%s" file is missing' % filename- if mtime == os.path.getmtime(filename):- raise GitMergeException, 'The "%s" file was not modified' % filename- finally:- # keep the merge stages?- if str(keeporig) != 'yes':- __remove_stages(filename)--def clean_up(filename):- """Remove merge conflict stages if they were generated.- """- if str(keeporig) == 'yes':- __remove_stages(filename)--def merge(filename):- """Merge one file if interactive is allowed or check out the stages- if keeporig is set.- """- if str(autoimerge) == 'yes':- try:- interactive_merge(filename)- except GitMergeException, ex:- out.error(str(ex))- return False- return True-- if str(keeporig) == 'yes':- __checkout_stages(filename)-- return False
This patch adds the IndexAndWorktree.mergetool() function responsible
for calling 'git mergetool' to interactively solve conflicts. The
function may also be called from IndexAndWorktree.merge() if the
standard 'git merge-recursive' fails and 'interactive == True'. The
'allow_interactive' parameter is passed to Transaction.push_patch() from
the functions allowing interactive merging.
Signed-off-by: Catalin Marinas <redacted>
---
stgit/commands/edit.py | 2 +-
stgit/commands/goto.py | 2 +-
stgit/lib/git.py | 21 ++++++++++++++++++---
stgit/lib/transaction.py | 7 +++++--
4 files changed, 25 insertions(+), 7 deletions(-)
@@ -824,7 +824,7 @@ class IndexAndWorktree(RunWithEnvCwd):).discard_output()exceptrun.RunException:raiseCheckoutException('Index/workdir dirty')-defmerge(self,base,ours,theirs):+defmerge(self,base,ours,theirs,interactive=False):assertisinstance(base,Tree)assertisinstance(ours,Tree)assertisinstance(theirs,Tree)
@@ -838,10 +838,25 @@ class IndexAndWorktree(RunWithEnvCwd):output=r.output_lines()ifr.exitcode:# There were conflicts-conflicts=[lforlinoutputifl.startswith('CONFLICT')]-raiseMergeConflictException(conflicts)+ifinteractive:+self.mergetool()+else:+conflicts=[lforlinoutputifl.startswith('CONFLICT')]+raiseMergeConflictException(conflicts)exceptrun.RunException,e:raiseMergeException('Index/worktree dirty')+defmergetool(self,files=()):+"""Invoke 'git mergetool' on the current IndexAndWorktree to resolve+anyoutstandingconflicts.If'not files',allthefilesinan+unmergedstatewillbeprocessed."""+err=os.system('git mergetool %s'%' '.join(files))+# check for unmerged entries (prepend 'CONFLICT ' for consistency with+# merge())+conflicts=['CONFLICT '+fforfinself.index.conflicts()]+ifconflicts:+raiseMergeConflictException(conflicts)+eliferr:+raiseMergeException('"git mergetool" failed, exit code: %d'%err)defchanged_files(self,tree,pathlimits=[]):"""Return the set of files in the worktree that have changed withrespecttoC{tree}.Thelistingisoptionallyrestrictedto
@@ -8,6 +8,7 @@ from stgit import exception, utilsfromstgit.utilsimportany,allfromstgit.outimport*fromstgit.libimportgit,log+fromstgit.configimportconfigclassTransactionException(exception.StgException):"""Exception raised when something goes wrong with a
@@ -296,7 +297,7 @@ class StackTransaction(object):out.info('Deleted %s%s'%(pn,s))returnpopped-defpush_patch(self,pn,iw=None):+defpush_patch(self,pn,iw=None,allow_interactive=False):"""Attempt to push the named patch. If this results in conflicts,haltsthetransaction.Ifindex+worktreearegiven,spillanyconflictstothem."""
@@ -319,7 +320,9 @@ class StackTransaction(object):exceptgit.CheckoutException:self.__halt('Index/worktree dirty')try:-iw.merge(base,ours,theirs)+interactive=allow_interactiveand \+config.get('stgit.autoimerge')=='yes'+iw.merge(base,ours,theirs,interactive=interactive)tree=iw.index.write_tree()self.__current_tree=trees=' (modified)'
This is done by default, unless the --keep option is passed, for
consistency with the "pop" command. The index is checked in the
Transaction.run() function so that other commands could benefit from
this feature (off by default).
This behaviour can be overridden by setting the stgit.autokeep option.
Signed-off-by: Catalin Marinas <redacted>
---
examples/gitconfig | 3 +++
stgit/argparse.py | 5 +++++
stgit/commands/goto.py | 7 +++++--
stgit/lib/git.py | 14 ++++++++++++--
stgit/lib/transaction.py | 10 +++++++++-
t/t2300-refresh-subdir.sh | 2 +-
t/t2800-goto-subdir.sh | 4 ++--
t/t3000-dirty-merge.sh | 2 +-
8 files changed, 38 insertions(+), 9 deletions(-)
@@ -103,6 +103,9 @@ # -O/--diff-opts). For example, -M turns on rename detection. #diff-opts = -M+ # Behave as if the --keep option is always passed+ #autokeep = no+ [mail "alias"] # E-mail aliases used with the 'mail' command git = git@vger.kernel.org
@@ -220,6 +220,11 @@ def _person_opts(person, short):defauthor_options():return_person_opts('author','auth')+defkeep_option():+return[opt('-k','--keep',action='store_true',+short='Keep the local changes',+default=config.get('stgit.autokeep')=='yes')]+classCompgenBase(object):defactions(self,var):returnset()defwords(self,var):returnset()
@@ -18,6 +18,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USAfromstgit.commandsimportcommonfromstgit.libimporttransactionfromstgitimportargparse+fromstgit.argparseimportopthelp='Push or pop patches to the given one'kind='stack'
@@ -27,7 +28,7 @@ Push/pop patches to/from the stack until the one given on the commandlinebecomescurrent."""args=[argparse.other_applied_patches,argparse.unapplied_patches]-options=[]+options=argparse.keep_option()directory=common.DirectoryHasRepositoryLib()
@@ -706,9 +706,11 @@ class Index(RunWithEnv):).output_one_line())exceptrun.RunException:raiseMergeException('Conflicting merge')-defis_clean(self):+defis_clean(self,tree):+"""Check whether the index is clean relative to the given treeish."""try:-self.run(['git','update-index','--refresh']).discard_output()+self.run(['git','diff-index','--quiet','--cached',tree.sha1]+).discard_output()exceptrun.RunException:returnFalseelse:
@@ -858,6 +860,14 @@ class IndexAndWorktree(RunWithEnvCwd):cmd=['git','update-index','--remove']self.run(cmd+['-z','--stdin']).input_nulterm(paths).discard_output()+defworktree_clean(self):+"""Check whether the worktree is clean relative to index."""+try:+self.run(['git','update-index','--refresh']).discard_output()+exceptrun.RunException:+returnFalse+else:+returnTrueclassBranch(object):"""Represents a Git branch."""
@@ -75,7 +75,8 @@ class StackTransaction(object):yourrefsandindex+worktree,orfailwithouthavingdoneanything."""def__init__(self,stack,msg,discard_changes=False,-allow_conflicts=False,allow_bad_head=False):+allow_conflicts=False,allow_bad_head=False,+check_clean_iw=None):"""Create a new L{StackTransaction}.@paramdiscard_changes:Discardanychangesinindex+worktree
@@ -102,6 +103,8 @@ class StackTransaction(object):self.__temp_index=self.temp_index_tree=Noneifnotallow_bad_head:self.__assert_head_top_equal()+ifcheck_clean_iw:+self.__assert_index_worktree_clean(check_clean_iw)stack=property(lambdaself:self.__stack)patches=property(lambdaself:self.__patches)def__set_applied(self,val):
@@ -147,6 +150,11 @@ class StackTransaction(object):'This can happen if you modify a branch with git.','"stg repair --help" explains more about what to do next.')self.__abort()+def__assert_index_worktree_clean(self,iw):+ifnotiw.worktree_clean()or \+notiw.index.is_clean(self.stack.head):+self.__halt('Repository not clean. Use "refresh" or '+'"status --reset"')def__checkout(self,tree,iw,allow_bad_head):ifnotallow_bad_head:self.__assert_head_top_equal()
@@ -16,11 +16,11 @@ along with this program; if not, write to the Free SoftwareFoundation,Inc.,59TemplePlace,Suite330,Boston,MA02111-1307USA"""-importsys,os+importsysfromstgit.argparseimportopt-fromstgit.commands.commonimport*-fromstgit.utilsimport*-fromstgitimportargparse,stack,git+fromstgit.commandsimportcommon+fromstgit.libimporttransaction+fromstgitimportargparsehelp='Push patches to the top, even if applied'kind='stack'
@@ -36,25 +36,20 @@ args = [argparse.patch_range(argparse.applied_patches,argparse.unapplied_patches)]options=[opt('-s','--series',action='store_true',-short='Rearrange according to a series file')]+short='Rearrange according to a series file')+]+argparse.keep_option()-directory=DirectoryGotoToplevel(log=True)+directory=common.DirectoryHasRepositoryLib()deffunc(parser,options,args):-"""Pops and pushed to make the named patch the topmost patch+"""Reorder patches to make the named patch the topmost one."""args_nr=len(args)if(options.seriesandargs_nr>1) \or(notoptions.seriesandargs_nr==0):parser.error('incorrect number of arguments')-check_local_changes()-check_conflicts()-check_head_top_equal(crt_series)--unapplied=crt_series.get_unapplied()-applied=crt_series.get_applied()-all=unapplied+applied+stack=directory.repository.current_stackifoptions.series:ifargs_nr:
@@ -68,35 +63,23 @@ def func(parser, options, args):ifpatch:patches.append(patch)else:-patches=parse_patches(args,all)--# working with "topush" patches in reverse order might be a bit-# more efficient for large series but the main reason is for the-# "topop != topush" comparison to work-patches.reverse()--topush=[]-topop=[]--forpinpatches:-whilepinapplied:-top=applied.pop()-ifnottopinpatches:-topush.append(top)-topop.append(top)-topush=patches+topush--# remove common patches to avoid unnecessary pop/push-whiletopushandtopop:-iftopush[-1]!=topop[-1]:-break-topush.pop()-topop.pop()--# check whether the operation is really needed-iftopop!=topush:-iftopop:-pop_patches(crt_series,topop)-iftopush:-topush.reverse()-push_patches(crt_series,topush)+patches=common.parse_patches(args,stack.patchorder.all)++ifnotpatches:+raisecommon.CmdException('No patches to float')++applied=[pforpinstack.patchorder.appliedifpnotinpatches]+ \+patches+unapplied=[pforpinstack.patchorder.unappliedifpnotinpatches]+hidden=list(stack.patchorder.hidden)++iw=stack.repository.default_iw+clean_iw=notoptions.keepandiworNone+trans=transaction.StackTransaction(stack,'sink',+check_clean_iw=clean_iw)++try:+trans.reorder_patches(applied,unapplied,hidden,iw)+excepttransaction.TransactionHalted:+pass+returntrans.run(iw)
@@ -16,11 +16,10 @@ along with this program; if not, write to the Free SoftwareFoundation,Inc.,59TemplePlace,Suite330,Boston,MA02111-1307USA"""-importsys,osfromstgit.argparseimportopt-fromstgit.commands.commonimport*-fromstgit.utilsimport*-fromstgitimportargparse,stack,git+fromstgit.commandsimportcommon+fromstgit.libimporttransaction+fromstgitimportargparsehelp='Send patches deeper down the stack'kind='stack'
@@ -51,57 +50,48 @@ options = [opt('-t','--to',metavar='TARGET',args=[argparse.applied_patches],short='Sink patches below the TARGET patch',long="""Specifyatargetpatchtoplacethepatchesbelow,insteadof-sinkingthemtothebottomofthestack.""")]+sinkingthemtothebottomofthestack.""")+]+argparse.keep_option()-directory=DirectoryGotoToplevel(log=True)+directory=common.DirectoryHasRepositoryLib()deffunc(parser,options,args):"""Sink patches down the stack."""+stack=directory.repository.current_stack-check_local_changes()-check_conflicts()-check_head_top_equal(crt_series)+ifoptions.toandnotoptions.toinstack.patchorder.applied:+raisecommon.CmdException('Cannot sink below %s since it is not applied'+%options.to)-oldapplied=crt_series.get_applied()-unapplied=crt_series.get_unapplied()-all=oldapplied+unapplied+iflen(args)>0:+patches=common.parse_patches(args,stack.patchorder.all)+else:+# current patch+patches=list(stack.patchorder.applied[-1:])-ifoptions.toandnotoptions.toinoldapplied:-raiseCmdException('Cannot sink below %s, since it is not applied'-%options.to)+ifnotpatches:+raisecommon.CmdException('No patches to sink')+ifoptions.toandoptions.toinpatches:+raisecommon.CmdException('Cannot have a sinked patch as target')-iflen(args)>0:-patches=parse_patches(args,all)+applied=[pforpinstack.patchorder.appliedifpnotinpatches]+ifoptions.to:+insert_idx=applied.index(options.to)else:-current=crt_series.get_current()-ifnotcurrent:-raiseCmdException('No patch applied')-patches=[current]--before_patches=after_patches=[]--# pop necessary patches-ifoldapplied:-ifoptions.to:-pop_idx=oldapplied.index(options.to)-else:-pop_idx=0-after_patches=[pforpinoldapplied[pop_idx:]ifpnotinpatches]--# find the deepest patch to pop-sink_applied=[pforpinoldappliedifpinpatches]-ifsink_applied:-sinked_idx=oldapplied.index(sink_applied[0])-ifsinked_idx<pop_idx:-# this is the case where sink brings patches forward-before_patches=[pforpinoldapplied[sinked_idx:pop_idx]-ifpnotinpatches]-pop_idx=sinked_idx--crt_series.pop_patch(oldapplied[pop_idx])--push_patches(crt_series,before_patches)-push_patches(crt_series,patches)-ifnotoptions.nopush:-push_patches(crt_series,after_patches)+insert_idx=0+applied=applied[:insert_idx]+patches+applied[insert_idx:]++unapplied=[pforpinstack.patchorder.unappliedifpnotinpatches]+hidden=list(stack.patchorder.hidden)++iw=stack.repository.default_iw+clean_iw=notoptions.keepandiworNone+trans=transaction.StackTransaction(stack,'sink',+check_clean_iw=clean_iw)++try:+trans.reorder_patches(applied,unapplied,hidden,iw)+excepttransaction.TransactionHalted:+pass+returntrans.run(iw)
From: Karl Hasselström <hidden> Date: 2016-06-15 22:46:23
On 2009-03-12 12:08:56 +0000, Catalin Marinas wrote:
This is done by default, unless the --keep option is passed, for
consistency with the "pop" command. The index is checked in the
Transaction.run() function so that other commands could benefit from
this feature (off by default).
This behaviour can be overridden by setting the stgit.autokeep option.
Two small nits; otherwise,
Acked-by: Karl Hasselström [off-list ref]
- trans = transaction.StackTransaction(stack, 'goto')
+ clean_iw = not options.keep and iw or None
Add some parentheses here, please! I know that "and" binds tighter
than "or", but I have to think for too long to remember which is
which, and I'll bet I'm not alone ...
+ def __assert_index_worktree_clean(self, iw):
+ if not iw.worktree_clean() or \
+ not iw.index.is_clean(self.stack.head):
+ self.__halt('Repository not clean. Use "refresh" or '
+ '"status --reset"')
"Repository" is misleading here. Maybe something like
ix_c = iw.index.is_clean(self.stack.head)
wt_c = iw.worktree_clean()
if not ix_c or not wt_c:
self.__halt('%s not clean. Use "refresh" or "status --reset"'
% { (False, True): 'Index', (True, False): 'Worktree',
(False, False): 'Index and worktree' }[(ix_c, wt_c)])
--
Karl Hasselström, kha@treskal.com
www.treskal.com/kalle
From: Karl Hasselström <hidden> Date: 2016-06-15 22:46:23
On 2009-03-12 12:09:02 +0000, Catalin Marinas wrote:
Since Git already has a tool for interactively solving conflicts
which is highly customisable, there is no need to duplicate this
feature via the i3merge and i2merge configuration options. The
user-visible change is that now mergetool is invoked rather than the
previously customised interactive merging tool.
I agree wholeheartedly with the idea. Just one issue:
+def mergetool(files = ()):
+ """Invoke 'git mergetool' to resolve any outstanding conflicts. If 'not
+ files', all the files in an unmerged state will be processed."""
+ err = os.system('git mergetool %s' % ' '.join(files))
+ # check for unmerged entries (prepend 'CONFLICT ' for consistency with
+ # merge_recursive())
+ conflicts = ['CONFLICT ' + f for f in get_conflicts()]
+ if conflicts:
Mmm, os.system()? That'll break things as soon as we have a file name
with a space in it. I'm pretty sure there's something in stgit.run
that you could use.
--
Karl Hasselström, kha@treskal.com
www.treskal.com/kalle
From: Karl Hasselström <hidden> Date: 2016-06-15 22:46:23
On 2009-03-12 12:09:07 +0000, Catalin Marinas wrote:
This patch adds the IndexAndWorktree.mergetool() function responsible
for calling 'git mergetool' to interactively solve conflicts. The
function may also be called from IndexAndWorktree.merge() if the
standard 'git merge-recursive' fails and 'interactive == True'. The
'allow_interactive' parameter is passed to Transaction.push_patch() from
the functions allowing interactive merging.
Nicely done with the "interactive" and "allow_interactive" arguments;
the policy and the implementation end up at the right levels.
# There were conflicts
- conflicts = [l for l in output if l.startswith('CONFLICT')]
- raise MergeConflictException(conflicts)
+ if interactive:
+ self.mergetool()
+ else:
+ conflicts = [l for l in output if l.startswith('CONFLICT')]
+ raise MergeConflictException(conflicts)
Does the merge tool always resolve all conflicts? If it doesn't, the
two lines in the "else" branch should probably be run unconditionally.
except run.RunException, e:
raise MergeException('Index/worktree dirty')
+ def mergetool(self, files = ()):
+ """Invoke 'git mergetool' on the current IndexAndWorktree to resolve
+ any outstanding conflicts. If 'not files', all the files in an
+ unmerged state will be processed."""
+ err = os.system('git mergetool %s' % ' '.join(files))
Look at how the surrounding code calls git. os.system() will do nasty
things with filenames that require quoting, such as for example
"; rm -rf ~/".
+ # check for unmerged entries (prepend 'CONFLICT ' for consistency with
+ # merge())
+ conflicts = ['CONFLICT ' + f for f in self.index.conflicts()]
+ if conflicts:
+ raise MergeConflictException(conflicts)
+ elif err:
+ raise MergeException('"git mergetool" failed, exit code: %d' % err)
Ah, you take care of conflicts here too. Hmm. I guess that's fine too,
though there is some code duplication. Maybe a helper function that
takes output as a parameter, and raises MergeConflictException if
necessary?
+ interactive = allow_interactive and \
+ config.get('stgit.autoimerge') == 'yes'
Small style nit: backslash line continuations are ugly. :-)
If you put parentheses around the expression, you can break the line
without a backslash.
--
Karl Hasselström, kha@treskal.com
www.treskal.com/kalle
From: Karl Hasselström <hidden> Date: 2016-06-15 22:46:23
Nicely done.
Acked-by: Karl Hasselström [off-list ref]
On 2009-03-12 12:09:13 +0000, Catalin Marinas wrote:
+ applied = applied[:insert_idx] + patches + applied[insert_idx:]
+
+ unapplied = [p for p in stack.patchorder.unapplied if p not in patches]
+ hidden = list(stack.patchorder.hidden)
+
+ iw = stack.repository.default_iw
+ clean_iw = not options.keep and iw or None
+ trans = transaction.StackTransaction(stack, 'sink',
+ check_clean_iw = clean_iw)
+
+ try:
+ trans.reorder_patches(applied, unapplied, hidden, iw)
Hmm. We should maybe have a default value for hidden: the current list
of patches. Not changing the hidden patches is a common operation.
--
Karl Hasselström, kha@treskal.com
www.treskal.com/kalle
From: Karl Hasselström <hidden> Date: 2016-06-15 22:46:23
Nice job here too.
On 2009-03-12 12:09:18 +0000, Catalin Marinas wrote:
options = [
opt('-s', '--series', action = 'store_true',
- short = 'Rearrange according to a series file')]
+ short = 'Rearrange according to a series file')
+ ] + argparse.keep_option()
This flag should take the filename as a parameter, both because it's
the right thing to do and because it'll make the tab completion work
right (as is, it'll complete on patch names after the -s flag).
Something like
opt('-s', '--series', type = 'string')
ought to do it.
+ applied = [p for p in stack.patchorder.applied if p not in patches] + \
+ patches
+ unapplied = [p for p in stack.patchorder.unapplied if p not in patches]
It may be just me, but I find "not p in patches" more readable than "p
not in patches". Oh, and the backslash.
Feel free to ignore, of course. :-)
+ hidden = list(stack.patchorder.hidden)
+
+ iw = stack.repository.default_iw
+ clean_iw = not options.keep and iw or None
+ trans = transaction.StackTransaction(stack, 'sink',
+ check_clean_iw = clean_iw)
+
+ try:
+ trans.reorder_patches(applied, unapplied, hidden, iw)
That default value for hidden would've come in handy here too!
--
Karl Hasselström, kha@treskal.com
www.treskal.com/kalle
On 2009-03-12 12:08:56 +0000, Catalin Marinas wrote:
quoted
+ def __assert_index_worktree_clean(self, iw):
+ if not iw.worktree_clean() or \
+ not iw.index.is_clean(self.stack.head):
+ self.__halt('Repository not clean. Use "refresh" or '
+ '"status --reset"')
"Repository" is misleading here. Maybe something like
ix_c = iw.index.is_clean(self.stack.head)
wt_c = iw.worktree_clean()
if not ix_c or not wt_c:
self.__halt('%s not clean. Use "refresh" or "status --reset"'
% { (False, True): 'Index', (True, False): 'Worktree',
(False, False): 'Index and worktree' }[(ix_c, wt_c)])
I added two separate if's as I don't find the above readable :-)
if not iw.worktree_clean():
self.__halt('Worktree not clean. Use "refresh" or "status --reset"')
if not iw.index.is_clean(self.stack.head):
self.__halt('Index not clean. Use "refresh" or "status --reset"')
def __checkout(self, tree, iw, allow_bad_head):
--
Catalin
On 2009-03-12 12:09:07 +0000, Catalin Marinas wrote:
quoted
# There were conflicts
- conflicts = [l for l in output if l.startswith('CONFLICT')]
- raise MergeConflictException(conflicts)
+ if interactive:
+ self.mergetool()
+ else:
+ conflicts = [l for l in output if l.startswith('CONFLICT')]
+ raise MergeConflictException(conflicts)
Does the merge tool always resolve all conflicts? If it doesn't, the
two lines in the "else" branch should probably be run unconditionally.
[...]
quoted
+ # check for unmerged entries (prepend 'CONFLICT ' for consistency with
+ # merge())
+ conflicts = ['CONFLICT ' + f for f in self.index.conflicts()]
+ if conflicts:
+ raise MergeConflictException(conflicts)
+ elif err:
+ raise MergeException('"git mergetool" failed, exit code: %d' % err)
Ah, you take care of conflicts here too. Hmm. I guess that's fine too,
though there is some code duplication. Maybe a helper function that
takes output as a parameter, and raises MergeConflictException if
necessary?
The non-interactive path assumes that there are conflicts if "git
merge-recursive" returned an error and it simply splits the output if
this command. The mergetool path has to run "git ls-files --unmerged"
to check if there were any left conflicts. I wouldn't call "git
ls-files" in the first case as we already have the information.
--
Catalin
On 2009-03-12 12:09:18 +0000, Catalin Marinas wrote:
quoted
options = [
opt('-s', '--series', action = 'store_true',
- short = 'Rearrange according to a series file')]
+ short = 'Rearrange according to a series file')
+ ] + argparse.keep_option()
This flag should take the filename as a parameter, both because it's
the right thing to do and because it'll make the tab completion work
right (as is, it'll complete on patch names after the -s flag).
Something like
opt('-s', '--series', type = 'string')
ought to do it.
This command was accepting series via the stdin as well (maybe for
easier use in other scripts or from stgit.el). Anyway, it doesn't seem
to make any difference with the bash completion. It still tries to
complete patches but when this fails bash lists files if the prefix
matches some.
--
Catalin
From: Karl Hasselström <hidden> Date: 2016-06-15 22:46:24
On 2009-03-16 14:56:11 +0000, Catalin Marinas wrote:
2009/3/13 Karl Hasselström [off-list ref]:
quoted
"Repository" is misleading here. Maybe something like
ix_c = iw.index.is_clean(self.stack.head)
wt_c = iw.worktree_clean()
if not ix_c or not wt_c:
self.__halt('%s not clean. Use "refresh" or "status --reset"'
% { (False, True): 'Index', (True, False): 'Worktree',
(False, False): 'Index and worktree' }[(ix_c, wt_c)])
I added two separate if's as I don't find the above readable :-)
if not iw.worktree_clean():
self.__halt('Worktree not clean. Use "refresh" or "status --reset"')
if not iw.index.is_clean(self.stack.head):
self.__halt('Index not clean. Use "refresh" or "status --reset"')
It's actually quite nice once you get used to the idea of ephemeral
dictionaries just for selection inside an expression ... :-)
Your version doesn't generate the "Your index and worktree are both
dirty" warning, but I guess that's OK.
--
Karl Hasselström, kha@treskal.com
www.treskal.com/kalle
From: Karl Hasselström <hidden> Date: 2016-06-15 22:46:24
On 2009-03-16 15:03:09 +0000, Catalin Marinas wrote:
2009/3/13 Karl Hasselström [off-list ref]:
quoted
On 2009-03-12 12:09:07 +0000, Catalin Marinas wrote:
quoted
# There were conflicts
- conflicts = [l for l in output if l.startswith('CONFLICT')]
- raise MergeConflictException(conflicts)
+ if interactive:
+ self.mergetool()
+ else:
+ conflicts = [l for l in output if l.startswith('CONFLICT')]
+ raise MergeConflictException(conflicts)
Does the merge tool always resolve all conflicts? If it doesn't,
the two lines in the "else" branch should probably be run
unconditionally.
[...]
quoted
quoted
+ # check for unmerged entries (prepend 'CONFLICT ' for consistency with
+ # merge())
+ conflicts = ['CONFLICT ' + f for f in self.index.conflicts()]
+ if conflicts:
+ raise MergeConflictException(conflicts)
+ elif err:
+ raise MergeException('"git mergetool" failed, exit code: %d' % err)
Ah, you take care of conflicts here too. Hmm. I guess that's fine
too, though there is some code duplication. Maybe a helper
function that takes output as a parameter, and raises
MergeConflictException if necessary?
The non-interactive path assumes that there are conflicts if "git
merge-recursive" returned an error and it simply splits the output
if this command. The mergetool path has to run "git ls-files
--unmerged" to check if there were any left conflicts. I wouldn't
call "git ls-files" in the first case as we already have the
information.
I was thinking of a function you'd call with either the output of the
merge operation or ls-files depending on the code path. But maybe it's
not worth it.
--
Karl Hasselström, kha@treskal.com
www.treskal.com/kalle
From: Karl Hasselström <hidden> Date: 2016-06-15 22:46:24
On 2009-03-16 16:36:43 +0000, Catalin Marinas wrote:
2009/3/13 Karl Hasselström [off-list ref]:
quoted
On 2009-03-12 12:09:18 +0000, Catalin Marinas wrote:
quoted
options = [
opt('-s', '--series', action = 'store_true',
- short = 'Rearrange according to a series file')]
+ short = 'Rearrange according to a series file')
+ ] + argparse.keep_option()
This flag should take the filename as a parameter, both because
it's the right thing to do and because it'll make the tab
completion work right (as is, it'll complete on patch names after
the -s flag).
Something like
opt('-s', '--series', type = 'string')
ought to do it.
This command was accepting series via the stdin as well (maybe for
easier use in other scripts or from stgit.el).
Ah. Hmm, I'd prefer if it used "-" for that, for consistency. And
because I don't know how to make flags with optional parameters. :-/
Anyway, it doesn't seem to make any difference with the bash
completion. It still tries to complete patches but when this fails
bash lists files if the prefix matches some.
Hmm, that's not right. But I can have a look at it if you like.
--
Karl Hasselström, kha@treskal.com
www.treskal.com/kalle
On 2009-03-16 14:56:11 +0000, Catalin Marinas wrote:
quoted
if not iw.worktree_clean():
self.__halt('Worktree not clean. Use "refresh" or "status --reset"')
if not iw.index.is_clean(self.stack.head):
self.__halt('Index not clean. Use "refresh" or "status --reset"')
[...]
Your version doesn't generate the "Your index and worktree are both
dirty" warning, but I guess that's OK.
The iw.worktree_clean() only checks whether the worktree is clean
relative to the index (I just tried "git update-index --refresh" after
"git add <modified file>" and it returns 0).
--
Catalin
From: Karl Hasselström <hidden> Date: 2016-06-15 22:46:24
On 2009-03-17 10:51:08 +0000, Catalin Marinas wrote:
2009/3/17 Karl Hasselström [off-list ref]:
quoted
On 2009-03-16 14:56:11 +0000, Catalin Marinas wrote:
quoted
if not iw.worktree_clean():
self.__halt('Worktree not clean. Use "refresh" or "status --reset"')
if not iw.index.is_clean(self.stack.head):
self.__halt('Index not clean. Use "refresh" or "status --reset"')
[...]
quoted
Your version doesn't generate the "Your index and worktree are
both dirty" warning, but I guess that's OK.
The iw.worktree_clean() only checks whether the worktree is clean
relative to the index (I just tried "git update-index --refresh"
after "git add <modified file>" and it returns 0).
Yes, I know. The point I was trying to make was that your code doesn't
make a difference between
(iw.worktree_clean(), iw.index.is_clean(self.stack.head)) == (False, True)
and
(iw.worktree_clean(), iw.index.is_clean(self.stack.head)) == (False, False)
But as I said, it's not really important.
--
Karl Hasselström, kha@treskal.com
www.treskal.com/kalle