2012-12-18 11 views
9

Allgemeines Problem: Wie finde ich die Liste der Commits, die alle diese Commits als Vorfahren oder damit zusammenhängend die ersten Commits enthalten, die alle Commits enthalten diese Commits.Git-Commits finden, die mehrere spezifische Commits enthalten

Ich kann Zweig finden (ähnlich Tags), die die Commits für Zweige von der Suche enthält, die von git branch --contains <commit> für alle Commits im Satz zurückgegeben werden, aber git rev-list keine --contains Option. Effektiv suche ich nach einer Möglichkeit, die regulären --contains Argumente mit git rev-list zu kombinieren, und die Ausgabe auf commits einzuschränken, die alle die aufgeführten Commits enthalten, keine von ihnen (so funktioniert --contains normal).

Konkretes Beispiel: Bei verpflichtet a, b, c, wie finde ich die erste verpflichten, dass alle drei Commits in seiner Abstammung hat?

Zum Beispiel, wie finde ich die gegebene Commit markiert X?

* (master) 
| 
X 
|\ 
a * 
| | 
b c 
|/ 
* 
| 
* 

Ich gehe davon aus, dass eine Magie ich mit git rev-list tun kann, und möglicherweise mit der <commit1>...<commit2> Notation, aber ich kann nicht weiter funktionieren als das.

+0

ich nicht von einem einfachen (effizient) Weg, dies zu tun denken kann, kurz eine Liste aller zu erzeugen verschmelzen verpflichtet, und Testen jeden einer einzeln, um zu sehen, ob jedes der fraglichen Commits von dort erreichbar ist. Könnte relativ einfach geschrieben werden, aber es wäre * langsam *. Ich denke, dass eine neuere (d. H. 1.8+) Version von 'git' an einigen Stellen eine Option' --contains' hinzugefügt hat, die das Ganze ein wenig leichter machen könnte. – twalberg

+0

Gehören b und c zu verschiedenen Zweigen? – ShadyKiller

+0

@ShadyKiller: Im konkreten Beispiel ja; im Allgemeinen, nein. Alle drei können sich in demselben Zweig befinden (in diesem Fall wäre die Antwort nur das, was am nächsten kommt) oder verschiedene Zweige. Zum Teufel, es kann mehr oder weniger als drei Commits geben; das war eine relativ willkürliche Zahl. –

Antwort

1

Eine mögliche Lösung:

Use 'git merge-base a b c' erhalten das Commit als Ausgangspunkt in einem Anruf zu verwenden, um Rev-Liste; Wir nennen es $ MERGE_BASE.

Verwenden Sie den Aufruf 'git rev-list $ MERGE_BASE..HEAD', um alle Commits von ihrem gemeinsamen Vorfahren zu HEAD aufzulisten. Schleife durch diesen Ausgang (Pseudo-Code):

if commit == a || b || c 
    break 
else 
    $OLDEST_DESCENDANT = commit 
return $OLDEST_DESCENDANT 

Dies wird oben für Ihr Beispiel funktioniert, aber ein falsch positive geben, wenn sie zusammen nie wurden, wurden in der Commit unmittelbar nach dem jüngsten einem nicht fusionierte, b , c, oder wenn es mehrere Zusammenführungs-Commits gab, um a, b und c zusammenzubringen (wenn sie sich jeweils in ihrem eigenen Zweig befanden). Es gibt noch ein bisschen Arbeit, um den ältesten Nachkommen zu finden.

Sie sollten dann die oben mit etwas beginnt mit $ OLDEST_DESCENDANT und geht rückwärts in der DAG von ihm in Richtung HEAD (Rev-Liste - Reverse $ OLDEST_DESCENDANT ~ ..HEAD), testen, um zu sehen, dass die Ausgabe von 'rev -list $ MERGE_BASE ~ .. $ OLDEST enthält alle gewünschten Commits a, b und c (vielleicht gibt es einen besseren Weg zu testen, dass sie erreichbar sind als rev-list).

Wie Twalberg erwähnt, scheint das Testen der Commits individuell so nicht optimal und langsam, aber es ist ein Anfang. Dieser Ansatz hat gegenüber seiner Merge-Commit-List-Methode den Vorteil, dass er eine gültige Antwort liefert, wenn sich alle Eingabe-Commits in derselben Verzweigung befinden.

Die Leistung wird hauptsächlich durch die Abstände zwischen der Zusammenführungsbasis, dem Kopf, X und dem jüngsten des gewünschten Festschreibungssatzes (a, b und c) beeinflusst.

+0

Das sieht gut aus, ich hatte einfach keine Chance, mich hinzusetzen, den Pseudocode richtig zu schreiben und zu sehen, was passiert. –

-1

Wie wäre:

MERGE_BASE=`git merge-base A B C` 
git log $MERGE_BASE...HEAD --merges 

Angenommen, Sie haben nur 1 verschmelzen.Selbst wenn Sie mehrere Zusammenführungen haben, ist der älteste der, der Änderungen von allen drei commits enthält

+0

Dies funktioniert nur in sehr einfachen Szenarien, wenn das Revisionsdiagramm eine ernsthafte Komplexität aufweist (eine, die tatsächlich einen solchen Befehl erfordert), erhalten Sie nur eine kleinere Liste aller möglichen Zusammenführungen, die die eine sein könnten. Und das Commit, nach dem du suchst, ist nicht unbedingt eine Verschmelzung, sondern könnte eine der aufgeführten sein. – Chronial

+1

Du musstest mir keine -1 geben :(. Ich war zumindest teilweise korrekt – ShadyKiller

2

Ich denke, die Antwort auf diese Frage ist, dass git nicht dafür gemacht wurde. Git mag die Idee von "Kindern eines Commits" nicht wirklich, und dafür gibt es einen sehr guten Grund: Es ist nicht sehr gut definiert. Da ein Commit nichts über seine Kinder weiß, ist es ein sehr vages Set. Möglicherweise haben Sie nicht alle Zweige in Ihrem Repo und daher fehlen einige Kinder.

Gits interne Speicherstruktur macht auch die Suche nach den Kindern einer Commit eine ziemlich teure Operation, wie Sie die Revision Grafik aller Köpfe zu ihren entsprechenden Wurzeln gehen oder bis Sie alle Commits gesehen, deren Kinder Sie wissen möchten Über.

Das einzige Konzept dieser Art, das Git unterstützt, ist die Idee von einem Commit mit anderen Commit. Aber diese Funktion wird nur von sehr wenigen Git-Befehlen unterstützt (git branch ist eine davon). Und wo git es unterstützt, unterstützt es es nicht für willkürliche Commits, sondern nur für Branch Heads.

Dies alles mag eine ziemlich harte Einschränkung von Git erscheinen, aber in der Praxis stellt sich heraus, dass Sie die "Kinder" eines Commits nicht benötigen, aber normalerweise nur wissen müssen, welche Zweige ein bestimmtes Commit enthalten.


Das alles gesagt: Wenn Sie wirklich die Antwort auf Ihre Frage erhalten möchten, müssen Sie Ihr eigenes Skript schreiben, das es findet. Der einfachste Weg ist, mit der Ausgabe von git rev-list --parents --reverse --all zu beginnen. Wenn Sie diese Zeile für Zeile analysieren, erstellen Sie einen Baum und markieren für jeden Knoten, ob es sich um ein untergeordnetes Element der gesuchten Commits handelt. Sie tun dies, indem Sie die Commits selbst markieren, sobald Sie sie erfüllen, und dann diese Eigenschaft an alle ihre Kinder weiterleiten und so weiter.

Sobald Sie einen Commit haben, der mit allen Commits markiert ist, fügen Sie ihn Ihrer "Lösungsliste" hinzu und markieren alle seine untergeordneten Elemente als tot - sie dürfen keine ersten Commits mehr enthalten. Diese Eigenschaft wird dann auch an alle ihre Nachkommen weitergegeben.

Sie können hier ein wenig Speicher speichern, wenn Sie keine Teile des Baums speichern, die keine der von Ihnen angeforderten Commits enthalten.


bearbeiten Hacked einigen Python-Code

#!/usr/bin/python -O 
import os 
import sys 

if len(sys.argv) < 2: 
    print ("USAGE: {0} <list-of-revs>".format([sys.argv[0]])) 
    exit(1) 

rev_list = os.popen('git rev-list --parents --reverse --all') 

looking_for = os.popen('git rev-parse {0}' 
         .format(" ".join(sys.argv[1:]))).read().splitlines() 
solutions = set() 
commits = {} 

for line in rev_list: 
    line = line.strip().split(" ") 
    commit = set() 
    sha = line[0] 
    for parent in line[1:]: 
     if not parent in commits: 
      continue 
     commit.update(commits[parent]) 
     if parent in solutions: 
      commit.add("dead") 
    if sha in looking_for: 
     commit.add(sha) 
    if not "dead" in commit and commit.issuperset(looking_for): 
     solutions.add(sha) 
    # only keep commit if it's a child of looking_for 
    if len(commit) > 0: 
     commits[sha] = commit 

print "\n".join(solutions)