return series.get_patch(patch).get_old_top()
elif patch_id == 'bottom.old':
return series.get_patch(patch).get_old_bottom()
+ elif patch_id == 'log':
+ return series.get_patch(patch).get_log()
if patch == 'base' and patch_id == None:
return read_string(series.get_base_file())
except RevParseException:
--- /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, time
+from optparse import OptionParser, make_option
+from pydoc import pager
+from stgit.commands.common import *
+from stgit import stack, git
+
+help = 'display the patch changelog'
+usage = """%prog [options] [patch]
+
+List all the current and past commit ids of the given patch. The
+--graphical option invokes gitk instead of printing. The changelog
+commit messages have the form '<action> <new-patch-id>'. The <action>
+can be one of the following:
+
+ new - new patch created
+ refresh - local changes were added to the patch
+ push - the patch was cleanly pushed onto the stack
+ push(m) - the patch was pushed onto the stack with a three-way merge
+ 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."""
+
+options = [make_option('-b', '--branch',
+ help = 'use BRANCH instead of the default one'),
+ make_option('-p', '--patch',
+ help = 'show the refresh diffs',
+ action = 'store_true'),
+ make_option('-g', '--graphical',
+ help = 'run gitk instead of printing',
+ action = 'store_true')]
+
+def show_log(log, show_patch):
+ """List the patch changelog
+ """
+ commit = git.get_commit(log)
+ diff_str = ''
+ while commit:
+ descr = commit.get_log().rstrip()
+
+ if show_patch:
+ if descr.startswith('refresh') or descr.startswith('undo'):
+ diff_str = '%s%s\n' % (diff_str,
+ git.pretty_commit(commit.get_id_hash()))
+ else:
+ author_name, author_email, author_date = \
+ name_email_date(commit.get_author())
+ secs, tz = author_date.split()
+ date = '%s %s' % (time.ctime(int(secs)), tz)
+
+ print descr, date
+
+ parent = commit.get_parent()
+ if parent:
+ commit = git.get_commit(parent)
+ else:
+ commit = None
+
+ if show_patch and diff_str:
+ pager(diff_str.rstrip())
+
+def func(parser, options, args):
+ """Show the patch changelog
+ """
+ if len(args) == 0:
+ name = crt_series.get_current()
+ if not name:
+ raise CmdException, 'No patches applied'
+ elif len(args) == 1:
+ name = args[0]
+ if not name in crt_series.get_applied() + crt_series.get_unapplied():
+ raise CmdException, 'Unknown patch "%s"' % name
+ else:
+ parser.error('incorrect number of arguments')
+
+ patch = crt_series.get_patch(name)
+
+ log = patch.get_log()
+ if not log:
+ raise CmdException, 'No changelog for patch "%s"' % name
+
+ if options.graphical:
+ if os.system('gitk %s' % log) != 0:
+ raise CmdException, 'gitk execution failed'
+ else:
+ show_log(log, options.patch)
return self.__tree
def get_parent(self):
- return self.get_parents()[0]
+ parents = self.get_parents()
+ if parents:
+ return parents[0]
+ else:
+ return None
def get_parents(self):
return _output_lines('git-rev-list --parents --max-count=1 %s'
import stgit.commands.id
import stgit.commands.imprt
import stgit.commands.init
+import stgit.commands.log
import stgit.commands.mail
import stgit.commands.new
import stgit.commands.patches
'id': stgit.commands.id,
'import': stgit.commands.imprt,
'init': stgit.commands.init,
+ 'log': stgit.commands.log,
'mail': stgit.commands.mail,
'new': stgit.commands.new,
'patches': stgit.commands.patches,
'files',
'fold',
'import',
+ 'log',
'mail',
'new',
'pick',
self.__dir = os.path.join(self.__series_dir, self.__name)
self.__refs_dir = refs_dir
self.__top_ref_file = os.path.join(self.__refs_dir, self.__name)
+ self.__log_ref_file = os.path.join(self.__refs_dir,
+ self.__name + '.log')
def create(self):
os.mkdir(self.__dir)
os.remove(os.path.join(self.__dir, f))
os.rmdir(self.__dir)
os.remove(self.__top_ref_file)
+ if os.path.exists(self.__log_ref_file):
+ os.remove(self.__log_ref_file)
def get_name(self):
return self.__name
def rename(self, newname):
olddir = self.__dir
- old_ref_file = self.__top_ref_file
+ old_top_ref_file = self.__top_ref_file
+ old_log_ref_file = self.__log_ref_file
self.__name = newname
self.__dir = os.path.join(self.__series_dir, self.__name)
self.__top_ref_file = os.path.join(self.__refs_dir, self.__name)
+ self.__log_ref_file = os.path.join(self.__refs_dir,
+ self.__name + '.log')
os.rename(olddir, self.__dir)
- os.rename(old_ref_file, self.__top_ref_file)
+ os.rename(old_top_ref_file, self.__top_ref_file)
+ if os.path.exists(old_log_ref_file):
+ os.rename(old_log_ref_file, self.__log_ref_file)
def __update_top_ref(self, ref):
write_string(self.__top_ref_file, ref)
+ def __update_log_ref(self, ref):
+ write_string(self.__log_ref_file, ref)
+
def update_top_ref(self):
top = self.get_top()
if top:
address = os.environ['GIT_COMMITTER_EMAIL']
self.__set_field('commemail', address)
+ def get_log(self):
+ return self.__get_field('log')
+
+ def set_log(self, value, backup = False):
+ self.__set_field('log', value)
+ self.__update_log_ref(value)
+
class Series:
"""Class including the operations on series
author_name = None, author_email = None,
author_date = None,
committer_name = None, committer_email = None,
- backup = False):
+ backup = False, log = 'refresh'):
"""Generates a new commit for the given patch
"""
name = self.get_current()
patch.set_commname(committer_name)
patch.set_commemail(committer_email)
+ if log:
+ self.log_patch(patch, log)
+
return commit_id
def undo_refresh(self):
raise StackException, 'No refresh undo information available'
git.reset(tree_id = old_top, check_out = False)
- patch.restore_old_boundaries()
+ if patch.restore_old_boundaries():
+ self.log_patch(patch, 'undo')
def new_patch(self, name, message = None, can_edit = True,
unapplied = False, show_patch = False,
patch.set_commemail(committer_email)
if unapplied:
+ self.log_patch(patch, 'new')
+
patches = [patch.get_name()] + self.get_unapplied()
f = file(self.__unapplied_file, 'w+')
f.writelines([line + '\n' for line in patches])
f.close()
- else:
- if before_existing:
- insert_string(self.__applied_file, patch.get_name())
- if not self.get_current():
- self.__set_current(name)
- else:
- append_string(self.__applied_file, patch.get_name())
+ elif before_existing:
+ self.log_patch(patch, 'new')
+
+ insert_string(self.__applied_file, patch.get_name())
+ if not self.get_current():
self.__set_current(name)
+ else:
+ append_string(self.__applied_file, patch.get_name())
+ self.__set_current(name)
- self.refresh_patch(cache_update = False)
+ self.refresh_patch(cache_update = False, log = 'new')
def delete_patch(self, name):
"""Deletes a patch
# top != bottom always since we have a commit for each patch
if head == bottom:
- # reset the backup information
+ # reset the backup information. No logging since the
+ # patch hasn't changed
patch.set_bottom(head, backup = True)
patch.set_top(top, backup = True)
patch.set_bottom(head, backup = True)
patch.set_top(top, backup = True)
+
+ self.log_patch(patch, 'push(f)')
else:
top = head
# stop the fast-forwarding, must do a real merge
patch.set_top(head, backup = True)
modified = True
elif head == bottom:
- # reset the backup information
+ # reset the backup information. No need for logging
patch.set_bottom(bottom, backup = True)
patch.set_top(top, backup = True)
if not ex:
# if the merge was OK and no conflicts, just refresh the patch
# The GIT cache was already updated by the merge operation
- self.refresh_patch(cache_update = False)
+ if modified:
+ log = 'push(m)'
+ else:
+ log = 'push'
+ self.refresh_patch(cache_update = False, log = log)
else:
raise StackException, str(ex)
git.reset()
self.pop_patch(name)
- return patch.restore_old_boundaries()
+ ret = patch.restore_old_boundaries()
+ if ret:
+ self.log_patch(patch, 'undo')
+
+ return ret
def pop_patch(self, name, keep = False):
"""Pops the top patch from the stack
f.close()
else:
raise StackException, 'Unknown patch "%s"' % oldname
+
+ def log_patch(self, patch, message):
+ """Generate a log commit for a patch
+ """
+ top = git.get_commit(patch.get_top())
+ msg = '%s\t%s' % (message, top.get_id_hash())
+
+ old_log = patch.get_log()
+ if old_log:
+ parents = [old_log]
+ else:
+ parents = []
+
+ log = git.commit(message = msg, parents = parents,
+ cache_update = False, tree_id = top.get_tree(),
+ allowempty = True)
+ patch.set_log(log)
--- /dev/null
+#!/bin/sh
+#
+# Copyright (c) 2006 Catalin Marinas
+#
+
+test_description='Test the patch history generation.
+
+'
+
+. ./test-lib.sh
+
+test_expect_success \
+ 'Initialize the StGIT repository' \
+ '
+ stg init
+ '
+
+test_expect_success \
+ 'Create the first patch' \
+ '
+ stg new foo -m "Foo Patch" &&
+ echo foo > test && echo foo2 >> test &&
+ stg add test &&
+ stg refresh
+ '
+
+test_expect_success \
+ 'Create the second patch' \
+ '
+ stg new bar -m "Bar Patch" &&
+ echo bar >> test &&
+ stg refresh
+ '
+
+test_expect_success \
+ 'Check the "new" and "refresh" logs' \
+ '
+ stg log foo | grep -q -e "^new" &&
+ stg log foo | grep -q -e "^refresh" &&
+ stg log | grep -q -e "^new" &&
+ stg log | grep -q -e "^refresh"
+ '
+
+test_expect_success \
+ 'Check the "push" log' \
+ '
+ stg pop &&
+ echo foo > test2 && stg add test2 && stg refresh &&
+ stg push &&
+ stg log | grep -q -e "^push "
+ '
+
+test_expect_success \
+ 'Check the "push(f)" log' \
+ '
+ stg pop &&
+ stg refresh -m "Foo2 Patch" &&
+ stg push &&
+ stg log | grep -q -e "^push(f) "
+ '
+
+test_expect_success \
+ 'Check the "push(m)" log' \
+ '
+ stg pop &&
+ echo foo2 > test && stg refresh &&
+ stg push &&
+ stg log | grep -q -e "^push(m) "
+ '
+
+test_expect_success \
+ 'Check the push "undo" log' \
+ '
+ stg push --undo &&
+ stg log bar | grep -q -e "^undo "
+ '
+
+test_expect_success \
+ 'Check the refresh "undo" log' \
+ '
+ stg refresh --undo &&
+ stg log | grep -q -e "^undo "
+ '
+
+test_done