Commit 0b887623 authored by luqui@chromium.org's avatar luqui@chromium.org

Added git footers tool to parse conventional metadata from git commits

BUG=407316

Review URL: https://codereview.chromium.org/521033002

git-svn-id: svn://svn.chromium.org/chrome/trunk/tools/depot_tools@291777 0039d316-1c4b-4281-b951-d872f2087c98
parent 9d2c880e
#!/usr/bin/env bash
# Copyright 2014 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
# git_footers.py -- Extract the conventional footers associated with a commit.
. $(type -P python_git_runner.sh)
#!/usr/bin/env python
# Copyright 2014 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import argparse
import re
import sys
from collections import defaultdict
import git_common as git
FOOTER_PATTERN = re.compile(r'^\s*([\w-]+): (.*)$')
CHROME_COMMIT_POSITION_PATTERN = re.compile(r'^([\w/-]+)@{#(\d+)}$')
GIT_SVN_ID_PATTERN = re.compile('^([^\s@]+)@(\d+)')
def normalize_name(header):
return '-'.join([ word.title() for word in header.strip().split('-') ])
def parse_footer(line):
match = FOOTER_PATTERN.match(line)
if match:
return (match.group(1), match.group(2))
else:
return None
def parse_footers(message):
"""Parses a git commit message into a multimap of footers."""
footer_lines = []
for line in reversed(message.splitlines()):
if line == '' or line.isspace():
break
footer_lines.append(line)
footers = map(parse_footer, footer_lines)
if not all(footers):
return defaultdict(list)
footer_map = defaultdict(list)
for (k, v) in footers:
footer_map[normalize_name(k)].append(v.strip())
return footer_map
def get_unique(footers, key):
key = normalize_name(key)
values = footers[key]
assert len(values) <= 1, 'Multiple %s footers' % key
if values:
return values[0]
else:
return None
def get_position(footers):
"""Get the chrome commit position from a footer multimap using a heuristic.
Returns:
A tuple of the branch and the position on that branch. For example,
Cr-Commit-Position: refs/heads/master@{#292272}
would give the return value ('refs/heads/master', 292272). If
Cr-Commit-Position is not defined, we try to infer the ref and position
from git-svn-id. The position number can be None if it was not inferrable.
"""
position = get_unique(footers, 'Cr-Commit-Position')
if position:
match = CHROME_COMMIT_POSITION_PATTERN.match(position)
assert match, 'Invalid Cr-Commit-Position value: %s' % position
return (match.group(1), match.group(2))
svn_commit = get_unique(footers, 'git-svn-id')
if svn_commit:
match = GIT_SVN_ID_PATTERN.match(svn_commit)
assert match, 'Invalid git-svn-id value: %s' % svn_commit
if re.match('.*/chrome/trunk/src$', match.group(1)):
return ('refs/heads/master', match.group(2))
branch_match = re.match('.*/chrome/branches/([\w/-]+)/src$', match.group(1))
if branch_match:
# svn commit numbers do not map to branches.
return ('refs/branch-heads/%s' % branch_match.group(1), None)
raise ValueError('Unable to infer commit position from footers')
def main(args):
parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter
)
parser.add_argument('ref')
g = parser.add_mutually_exclusive_group()
g.add_argument('--key', metavar='KEY',
help='Get all values for the given footer name, one per '
'line (case insensitive)')
g.add_argument('--position', action='store_true')
g.add_argument('--position-ref', action='store_true')
g.add_argument('--position-num', action='store_true')
opts = parser.parse_args(args)
message = git.run('log', '-1', '--format=%B', opts.ref)
footers = parse_footers(message)
if opts.key:
for v in footers.get(normalize_name(opts.key), []):
print v
elif opts.position:
pos = get_position(footers)
print '%s@{#%s}' % (pos[0], pos[1] or '?')
elif opts.position_ref:
print get_position(footers)[0]
elif opts.position_num:
pos = get_position(footers)
assert pos[1], 'No valid position for commit'
print pos[1]
else:
for k in footers.keys():
for v in footers[k]:
print '%s: %s' % (k, v)
if __name__ == '__main__':
sys.exit(main(sys.argv[1:]))
This diff is collapsed.
'\" t
.\" Title: git-freeze
.\" Author: [FIXME: author] [see http://docbook.sf.net/el/author]
.\" Generator: DocBook XSL Stylesheets v1.76.1 <http://docbook.sf.net/>
.\" Date: 09/02/2014
.\" Manual: Chromium depot_tools Manual
.\" Source: depot_tools c9f28eb
.\" Language: English
.\"
.TH "GIT\-FREEZE" "1" "09/02/2014" "depot_tools c9f28eb" "Chromium depot_tools Manual"
.\" -----------------------------------------------------------------
.\" * Define some portability stuff
.\" -----------------------------------------------------------------
.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.\" http://bugs.debian.org/507673
.\" http://lists.gnu.org/archive/html/groff/2009-02/msg00013.html
.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.\" -----------------------------------------------------------------
.\" * set default formatting
.\" -----------------------------------------------------------------
.\" disable hyphenation
.nh
.\" disable justification (adjust text to left margin only)
.ad l
.\" -----------------------------------------------------------------
.\" * MAIN CONTENT STARTS HERE *
.\" -----------------------------------------------------------------
.SH "NAME"
git-footers \- Extract meta\-information expressed as footers in a commit message\&.
.SH "SYNOPSIS"
.sp
.nf
\fIgit footers\fR [\-\-key FOOTER] REF
\fIgit footers\fR [\-\-position | \-\-position\-ref | \-\-position\-num] REF
.fi
.sp
.SH "DESCRIPTION"
.sp
git footers extracts information included in commit messages as "footers", which are roughly like HTTP headers except they are at the end\&. For example, a commit might look like:
.sp
.if n \{\
.RS 4
.\}
.nf
This is a fancy commit message\&.
.fi
.if n \{\
.RE
.\}
.sp
.if n \{\
.RS 4
.\}
.nf
Cr\-Commit\-Position: refs/heads/master@{#292272}
Tech\-Debt\-Introduced: 17 nanoMSOffices
.fi
.if n \{\
.RE
.\}
.sp
git footers knows how to extract this information\&.
.sp
Footers are order\-independent and can appear more than once\&. Thus they are treated as a multimap\&.
.SH "OPTIONS"
.sp
If no options are given, all footers are printed, with their names case\-normalized\&.
.PP
\-\-key FOOTER
.RS 4
Extract all the headers associated with the given key, and print one per line\&. If there are no footers with this key, produces no output and exits successfully\&.
.RE
.PP
\-\-position
.RS 4
Extract the Chrome commit position from the footers\&. This first attempts to get the value of the
Cr\-Commit\-Position
footer\&. If that doesn\(cqt exist then it tries a heuristic based on
Git\-Svn\-Id\&. Output is in one of the following forms:
.sp
.if n \{\
.RS 4
.\}
.nf
refs/heads/master@{#292272}
refs/branch\-heads/branchname
.fi
.if n \{\
.RE
.\}
.RE
.PP
\-\-position\-num
.RS 4
Extracts and prints the Chrome commit position number only (292272 in the example above)\&. Exits with an error if one cannot be found\&.
.RE
.PP
\-\-position\-ref
.RS 4
Extracts and prints the Chrome commit position ref name only (ref/heads/master
or
refs/branch\-heads/branchname
in the example above)\&.
.RE
.SH "EXAMPLE"
.sp
.sp
.if n \{\
.RS 4
.\}
.nf
\fB$ git footers HEAD\fR
Tech\-Debt\-Introduced: \-4 microMSOffices
Tech\-Debt\-Introduced: 17 microMSOffices
Cr\-Commit\-Position: refs/heads/master@{#292272}
\fB$ git footers \-\-key Tech\-Debt\-Introduced HEAD\fR
\-4 microMSOffices
17 microMSOffices
\fB$ git footers \-\-position HEAD\fR
refs/heads/master@{#292272}
\fB$ git footers \-\-position\-num HEAD\fR
292272
\fB$ git footers \-\-position\-ref HEAD\fR
refs/heads/master
.fi
.if n \{\
.RE
.\}
.sp
.SH "SEE ALSO"
.sp
\fBgit-number\fR(1)
.SH "CHROMIUM DEPOT_TOOLS"
.sp
Part of the chromium \fBdepot_tools\fR(7) suite\&. These tools are meant to assist with the development of chromium and related projects\&. Download the tools from \m[blue]\fBhere\fR\m[]\&\s-2\u[1]\d\s+2\&.
.SH "NOTES"
.IP " 1." 4
here
.RS 4
\%https://chromium.googlesource.com/chromium/tools/depot_tools.git
.RE
Extract meta-information expressed as footers in a commit message.
#!/usr/bin/env bash
. demo_repo.sh
add deleted_file
add unstaged_deleted_file
add modified_file
c 'I commited this and am proud of it.
Cr-Commit-Position: refs/heads/master@{#292272}
Tech-Debt-Introduced: 17 microMSOffices
Tech-Debt-Introduced: -4 microMSOffices'
run git footers HEAD
run git footers --key Tech-Debt-Introduced HEAD
run git footers --position HEAD
run git footers --position-num HEAD
run git footers --position-ref HEAD
git-freeze(1)
=============
NAME
----
git-footers -
include::_git-footers_desc.helper.txt[]
SYNOPSIS
--------
[verse]
'git footers' [--key FOOTER] REF
'git footers' [--position | --position-ref | --position-num] REF
DESCRIPTION
-----------
`git footers` extracts information included in commit messages as "footers",
which are roughly like HTTP headers except they are at the end. For example, a
commit might look like:
This is a fancy commit message.
Cr-Commit-Position: refs/heads/master@{#292272}
Tech-Debt-Introduced: 17 nanoMSOffices
`git footers` knows how to extract this information.
Footers are order-independent and can appear more than once. Thus they are
treated as a multimap.
OPTIONS
-------
If no options are given, all footers are printed, with their names
case-normalized.
--key FOOTER::
Extract all the headers associated with the given key, and print one per
line. If there are no footers with this key, produces no output and exits
successfully.
--position::
Extract the Chrome commit position from the footers. This first attempts
to get the value of the `Cr-Commit-Position` footer. If that doesn't exist
then it tries a heuristic based on `Git-Svn-Id`. Output is in one of the
following forms:
refs/heads/master@{#292272}
refs/branch-heads/branchname
--position-num::
Extracts and prints the Chrome commit position number only (292272 in the
example above). Exits with an error if one cannot be found.
--position-ref::
Extracts and prints the Chrome commit position ref name only
(`ref/heads/master` or `refs/branch-heads/branchname` in the example above).
EXAMPLE
-------
demo:1[]
SEE ALSO
--------
linkgit:git-number[1]
include::_footer.txt[]
// vim: ft=asciidoc:
#!/usr/bin/env python
"""Tests for git_footers."""
import os
import sys
import unittest
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import git_footers
class GitFootersTest(unittest.TestCase):
_message = """
This is my commit message. There are many like it, but this one is mine.
My commit message is my best friend. It is my life. I must master it.
"""
_position = 'refs/heads/master@{#292272}'
_position_footer = 'Cr-Commit-Position: %s\n' % _position
_git_svn_id = ('svn://svn.chromium.org/chrome/trunk/src@290386'
' 0039d316-1c4b-4281-b951-d872f2087c98')
_git_svn_id_footer = 'git-svn-id: %s\n' % _git_svn_id
_git_svn_id_branch = (
'svn://svn.chromium.org/chrome/branches/blabble/src@177288')
_git_svn_id_footer_branch = 'git-svn-id: %s\n' % _git_svn_id_branch
def testFootersBasic(self):
self.assertEqual(
git_footers.parse_footers(self._message), {})
self.assertEqual(
git_footers.parse_footers(self._message + self._position_footer),
{ 'Cr-Commit-Position': [ self._position ] })
self.assertEqual(
git_footers.parse_footers(self._message + self._git_svn_id_footer),
{ 'Git-Svn-Id': [ self._git_svn_id ] })
self.assertEqual(
git_footers.parse_footers(self._message + self._position_footer
+ self._position_footer),
{ 'Cr-Commit-Position': [ self._position, self._position ] })
def testTrunkHeuristic(self):
footers = git_footers.parse_footers(self._message + self._git_svn_id_footer)
self.assertEqual(
footers,
{ 'Git-Svn-Id': [ self._git_svn_id ] })
self.assertEqual(
git_footers.get_position(footers),
('refs/heads/master', '290386'))
def testBranchHeuristic(self):
footers = git_footers.parse_footers(self._message +
self._git_svn_id_footer_branch)
self.assertEqual(
footers,
{ 'Git-Svn-Id': [ self._git_svn_id_branch ] })
self.assertEqual(
git_footers.get_position(footers),
('refs/branch-heads/blabble', None))
if __name__ == '__main__':
unittest.main()
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment