* git-show-merge-path v1.0
@ 2010-09-24 19:03 Artur Skawina
0 siblings, 0 replies; only message in thread
From: Artur Skawina @ 2010-09-24 19:03 UTC (permalink / raw)
To: Git List
This started as a quick graph walking hack, but evolved into something that
can actually be useful.
git-show-merge-path can tell /if/, /how/ and /when/ a change became visible
from a certain branch or tag. Or a few hundred thereof.
"git-show-merge-path <commit> <targets>".
$ git-show-merge-path <commit> heads # checks all local branches
$ git-show-merge-path <commit> tags # checks all tags
$ git-show-merge-path <commit> heads/as # checks all local branches named 'as/...'
$ git-show-merge-path <commit> remotes/name
# checks all refs of the named remote
$ git-show-merge-path <commit> remotes # checks all refs of all remotes
$ git-show-merge-path <commit> heads/master
# checks the local 'master' branch
$ git-show-merge-path <commit> master # checks all 'master' branches
# (incl. remotes etc)
$ git-show-merge-path <commit> 'doh*' # checks all branches named 'doh...'
$ git-show-merge-path <commit> 'tags/v2.*' # checks all tags beginning w/ "v2."
$ git-show-merge-path <commit> '*' # checks every reference it finds
$ git-show-merge-path <commit> refs/heads/master # for nonhumans, or situations
# where the DWIM approach fails.
Eg inside a git.git clone "git-show-merge-path 829ef38 next" will say
how 829ef38 merged into 'origin/next' and "git-show-merge-path 829ef38 origin"
will report the status vs all the branches in that repo;
$ git-show-merge-path 829ef38 origin
12f7559e0634 M: 'dm/mergetool-vimdiff' => next 100922 16:36
\_ Merged into next and pu [v1.7.3-60-g12f7559]
Not reachable from HEAD, html, maint, man, master and todo
Checking which release included some change could look like this:
$ git-show-merge-path 657b6245b tags
44f9e6c6bc50 M: 'nouveau/for-airlied' => drm-linus 091223 00:28
f42ecb2808db M: 'drm-linus'@$KO/airlied/drm-2.6 091223 16:59
\_ Merged into v2.6.33, v2.6.33-rc2, v2.6.33-rc3, v2.6.33-rc4, v2.6.33-rc5,
v2.6.33-rc6, v2.6.33-rc7, v2.6.33-rc8, v2.6.33.1, v2.6.33.2, v2.6.34, v2.6.34-rc1,
v2.6.34-rc2, v2.6.34-rc3, v2.6.34-rc4, v2.6.34-rc5, v2.6.34-rc6, v2.6.34-rc7, v2.6.34.1,
v2.6.35, v2.6.35-rc1, v2.6.35-rc2, v2.6.35-rc3, v2.6.35-rc4, v2.6.35-rc5, v2.6.35-rc6,
v2.6.35.1, v2.6.35.2, v2.6.35.3, v2.6.36-rc1 and v2.6.36-rc2 [v2.6.33-rc1-266-gf42ecb2]
[followed by some uninteresting merges, and a long list of tagged releases which
do not contain this commit]
Note that the ref names it prints are simplified; if unsure, do not
rely on the DWIM target selection, just give it a full "refs/..." name.
artur
----------------------------------------------------------------------
#! /usr/bin/env pike
// git-show-merge-path <rev> [long-lived-branch(es)]
// v. 1.0
// Will show all external merge commits starting at <rev> until
// this commit appears on the specified branches. When that happens
// "Merged into <branchlist>" is printed. If <rev> is still
// unreachable from some of the branches then the search continues.
// If at least one of the branches does not contain <rev> then $0
// can and will print *all* merges (ie it won't stop at the last
// of the given branches containing this commit), followed by
// "Not reachable from <branchlist>". This is a feature (can be
// used to find leaks outside of the given branches).
//
#define die(a...) exit(1, "Aborting; %s"+a)
static mapping commits = ([]);
array parsecommits(string ... delim) {
array res = ({});
string id;
array lines = run("git", "rev-list", "--format=raw", "--ancestry-path",
"--date-order", @delim)/"\n";
foreach (lines, string line) {
array words = line/" ";
string h = words[0];
if (h=="commit") {
id = words[1];
if (!commits[id])
commits[id] = ([]);
res += ({id});
if (mapping bs = livebranches[id])
commits[id]["Branch"] += bs;
} else if (h=="") {
if (commits[id])
commits[id][""] += ({line});
}
else {
if (h=="parent" && !commits[id]["parent"] && commits[id]["Branch"]) {
string firstparent = words[1];
if (!commits[firstparent])
commits[firstparent] = ([ "Branch" : commits[id]["Branch"] ]);
else
commits[firstparent]["Branch"] += commits[id]["Branch"];
}
commits[id][h] += words[1..];
}
}
return res;
}
static mapping desc = ([]);
static mapping livebranches = ([]); // id : mapping(name:id)
static mapping branchnames = ([]); // name : id
int main(int argc, array argv) {
argv[1] = (run("git", "rev-parse", argv[1])/"\n")[0];
if (argc==2)
argv += ({"master"});
branchnames = git_refs(argv[2..]);
if (sizeof(branchnames)==0)
die("refs not found:%{ \"%s\"%}\n", "", argv[2..]);
foreach (branchnames; string b; string v)
livebranches[v] += ([b:v]);
array commit_list = parsecommits("^"+argv[1], @indices(livebranches));
commit_list = reverse(commit_list);
desc[argv[1]] = 1;
foreach (commit_list, string id) {
if (commits[id]["parent"]) {
foreach (commits[id]["parent"], string parent)
if (desc[parent])
desc[id] = 1;
if (sizeof(commits[id]["parent"])>1)
if (!desc[commits[id]["parent"][0]]) {
int comtime = (int)commits[id]["committer"][-2];
write("%.12s %-56.56s %.12s\n", id,
squeeze_subject(commits[id][""][1]),
cal->Second(comtime)->format_time_xshort());
}
if (mapping reached = commits[id]["Branch"]) {
reached = reached&branchnames;
if (sizeof(reached)>0) {
branchnames -= reached;
array refs = Array.sort_array(indices(reached));
write(" \\_ Merged into %s [%s]\n",
String.implode_nicely(refs),
git_describe(id) );
if (sizeof(branchnames)==0)
exit(0);
}
}
}
m_delete(commits, id);
}
array refs = Array.sort_array(indices(branchnames));
write(" Not reachable from %s\n", String.implode_nicely(refs));
}
// This can slow us down almost twice; open coding it
// into the history walk would be possible, but i'm
// not doing that for only a few 100ms gain (total)...
string git_describe(string id) {
return (run("git", "describe", id)/"\n")[0];
}
// Given glob pattern(s) ("m?st*r") return a mapping of
// all matching existing refs (symbolic:dereferenced_id)
mapping git_refs(array patterns) {
mapping res = ([]);
array tags = ({});
foreach (patterns; int i; string pattern)
if (pattern[0..4]!="refs/")
patterns[i] = "*/"+pattern;
foreach (run("git", "show-ref", "-d")/"\n", string line) {
array words = line/" ";
if (sizeof(words)<2)
break;
foreach (patterns, string pattern)
if (glob(pattern, words[1]) || glob(pattern+"/*", words[1])) {
if (words[1][0..9]!="refs/tags/")
res += ([ words[1] : words[0] ]);
else
tags += ({words[1]});
break;
}
}
if (sizeof(tags)) {
foreach (run("git", "show-ref", "-d", @tags)/"\n", string line)
if (line[sizeof(line)-3..]=="^{}") {
array words = line/" ";
res += ([ words[1][..sizeof(words[1])-4] : words[0] ]);
}
}
string prefix = String.common_prefix(indices(res));
if (prefix!="") {
int preflen = sizeof(prefix);
while (preflen && prefix[preflen-1]!='/')
preflen--;
foreach (res; string in; string val)
res[in[preflen..]] = m_delete(res, in);
}
return res;
}
string squeeze_subject(string subject) {
subject = String.trim_all_whites(subject);
subject = String.expand_tabs(subject);
foreach (sub_from_to, mapping m)
subject = replace(subject, m);
return subject;
}
static array(mapping) sub_from_to =
({
([
"Merge branch " : "Merge ",
"Merge remote branch " : "Merge ",
"Merge branches " : "MM:",
]),
([
"Merge " : "M: ",
"' of git:": "'@git:",
"' into ": "' => ",
]),
([
"git://git.kernel.org/pub/scm/linux/kernel/git/" : "$KO/",
"commit '" : "C'"
]),
});
string run(string ... cmdline) {
#if __REAL_MAJOR__<7 || __REAL_MAJOR__==7 && __REAL_MINOR__<8
string s = Process.popen(cmdline*" ");
if (s=="")
die("\n", cmdline*" ");
return s;
#else
mapping r;
mixed e = catch { r = Process.run( ({@cmdline}) ); };
if (e || r["exitcode"])
die("", e?e:r["stderr"]);
return r["stdout"];
#endif
}
static object cal = Calendar.ISO.set_timezone(Calendar.Timezone.UTC);
----------------------------------------------------------------------
^ permalink raw reply [flat|nested] only message in thread
only message in thread, other threads:[~2010-09-24 19:04 UTC | newest]
Thread overview: (only message) (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2010-09-24 19:03 git-show-merge-path v1.0 Artur Skawina
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).