Sebastians Blog http://sgaul.de Neues aus den Softwareminen... Sun, 13 Apr 2014 15:23:24 +0000 de-DE hourly 1 http://wordpress.org/?v=3.8.3 YAML-Manipulation mit Python http://sgaul.de/2014/04/13/yaml-manipulation-mit-python/ http://sgaul.de/2014/04/13/yaml-manipulation-mit-python/#comments Sun, 13 Apr 2014 15:23:24 +0000 http://sgaul.de/?p=2597 Weiterlesen ]]> Ich mache es zwanzig mal am Tag: t('admin.users.show.name'), die YAML-Datei öffnen, in denen sich die Übersetzungen befinden, den Pfad absuchen und ggf. fehlende Teile einfügen und zuletzt die Übersetzung hineinschreiben und speichern. Wirklich kreativ ist dabei nur das Formulieren, den Rest würde ich gern automatisieren. Dies sollte jedem Rails-Entwickler eine Hilfe sein.

Mein erster Schritt ist eine simple Kommandozeilen-Applikation, die eine YAML-Datei und einen Pfad liest, den Pfad findet oder erstellt, den aktuellen Wert anzeigt, einen neuen entgegennimmt, einträgt und alles in eine Datei schreibt. Das alles möglichst nah an Vim und Sublime, so dass man das ganze später in ein Plugin überführen kann. Somit bietet sich Python an, schon allein um mal wieder was neues zu nutzen.

YAML parsen und Pfade finden

Für diesen Job kommt PyYAML zum Einsatz. Leider machte mir die wirklich schlechte Dokumentation der Bibliothek zu schaffen, im Endeffekt ist es aber recht einfach:

import yaml

input_file = open(args.input_file, "r")
yaml_doc = yaml.load(input_file)

Die Rückgabe ist ein Dict (oder etwas das sich so verhält). In diesem Dictonary kann man nun den gewünschten Pfad suchen:

def find_or_create_parent(yaml_doc, search_string):
  yaml_component = yaml_doc
  for path_component in search_string.split(".")[:-1]:
    if path_component in yaml_component:
      if type(yaml_component[path_component]) is dict:
        yaml_component = yaml_component[path_component]
      else:
        sys.exit("Error: Component {0} of {1} exists but is not an object".format(
          path_component, search_string
        ))
    else:
      yaml_component[path_component] = dict()
      yaml_component = yaml_component[path_component]
  return yaml_component

Ich suche oder erstelle hier das Vaterobjekt des gesuchten Wertes. Ist ein Zwischenschritt kein Dict, breche ich aus Sicherheitsgründen ab, damit keine einfachen Werte oder aber Listen verloren gehen.

Werte lesen und ändern

Mit dem Vaterelement ist das Lesen und Schreiben recht einfach:

def current_value(yaml_doc, seach_string):
  parent =  find_or_create_parent(yaml_doc, search_string)
  if search_string.split(".")[-1] in parent:
    return parent[search_string.split(".")[-1]]
  else:
    return None

def set_value(yaml_doc, search_string, value):
  parent = find_or_create_parent(yaml_doc, search_string)
  parent[search_string.split(".")[-1]] = value

Mit diesen Funktionen kann man nun eine einfache CLI-Applikation formulieren:

print "Current value of {0}:".format(search_string)
print current_value(yaml_doc, search_string)
new_value = raw_input("Please enter new value: ").decode(sys.stdin.encoding)
set_value(yaml_doc, search_string, new_value)

Hier hat mir vor allem Pythons UTF-8-Handhabung Probleme gemacht, weshalb der Wert aus raw_input hier noch dekodiert werden muss. Keine Ahnung wie das schöner geht, in meinen Augen aber erschreckend dass man sich um sowas noch kümmern muss.

In Datei oder Terminal ausgeben

Für das Dumpen des YAML-Dokuments habe sich für mich folgende Einstellungen bewährt:

def dump(yaml_doc):
  return yaml.dump(yaml_doc, default_flow_style=False, allow_unicode=True, encoding="utf-8")

Auch hier scheint UTF-8 wieder etwas Unerwartetes zu sein. Aber es funktioniert:

if output_file_path is None:
  print ""
  print dump(yaml_doc)
else:
  output_file = open(output_file_path, "wb")
  output_file.write(dump(yaml_doc))
  print "Changes have been written into {0}".format(output_file_path)

Das Ergebnis

Mit Argparse konnte ich eine Demoanwendung erstellen, die im nächsten Schritt als Vorlage für ein Plugin für Vim oder Sublime dienen soll:

usage: test.py [-h] input_file search_string [output_file]

positional arguments:
  input_file
  search_string
  output_file

optional arguments:
  -h, --help     show this help message and exit

Das Skript kann mit einer Datei und einer Pfadangabe aufgerufen werden. Es zeigt den aktuellen Wert, erfragt einen neuen und schreibt dies in eine angegebene Datei oder das Terminal.

Offene Probleme

Mit dieser Lösung habe ich vor allem ein Problem: PyYAML liest und schreibt die Datei vollständig. Auch Zeilen, in denen nichts geändert wurde, werden daher an die präferierte PyYAML-Syntax angepasst. So wird beispielsweise

de:
  users:
    edit:
      text: |
        Hier können Sie Ihr Passwort anpassen.
        Lassen Sie die Felder frei, um keine Änderung vorzunehmen.

zu

de:                                                                                                                                                                                                  
  users:                                                                                                                                                                                             
    edit:
      change_password: Passwort ändern
      text: 'Hier können Sie Ihr Passwort anpassen.\nLassen Sie die Felder frei, um keine Änderung vorzunehmen.'

Das ist immer noch gültiges YAML, macht sich im Git-Diff aber nicht so schön.

Das gesamte Skript

#!/usr/bin/env python

import sys
import argparse
import yaml

parser = argparse.ArgumentParser()
parser.add_argument('input_file')
parser.add_argument('search_string')
parser.add_argument('output_file', default=None, nargs="?")
args = parser.parse_args()

input_file = open(args.input_file, "r")
search_string = args.search_string
output_file_path = args.output_file
yaml_doc = yaml.load(input_file)


def find_or_create_parent(yaml_doc, search_string):
  yaml_component = yaml_doc
  for path_component in search_string.split(".")[:-1]:
    if path_component in yaml_component:
      if type(yaml_component[path_component]) is dict:
        yaml_component = yaml_component[path_component]
      else:
        sys.exit("Error: Component {0} of {1} exists but is not an object".format(
          path_component, search_string
        ))
    else:
      yaml_component[path_component] = dict()
      yaml_component = yaml_component[path_component]
  return yaml_component

def current_value(yaml_doc, seach_string):
  parent =  find_or_create_parent(yaml_doc, search_string)
  if search_string.split(".")[-1] in parent:
    return parent[search_string.split(".")[-1]]
  else:
    return None

def set_value(yaml_doc, search_string, value):
  parent = find_or_create_parent(yaml_doc, search_string)
  parent[search_string.split(".")[-1]] = value

def dump(yaml_doc):
  return yaml.dump(yaml_doc, default_flow_style=False, allow_unicode=True, encoding="utf-8")


print "Current value of {0}:".format(search_string)
print current_value(yaml_doc, search_string)
new_value = raw_input("Please enter new value: ").decode(sys.stdin.encoding)
set_value(yaml_doc, search_string, new_value)

if output_file_path is None:
  print ""
  print dump(yaml_doc)
else:
  output_file = open(output_file_path, "wb")
  output_file.write(dump(yaml_doc))
  print "Changes have been written into {0}".format(output_file_path)

Abhängigkeiten

Natürlich muss Python installiert sein. Wenn sich das Skript über ein fehlendes YAML-Modul beschwert (ImportError: No module named yaml), lässt sich das in Ubuntu folgendermaßen nachrüsten:

sudo apt-get install python-yaml
]]>
http://sgaul.de/2014/04/13/yaml-manipulation-mit-python/feed/ 0
Guard erkennt keine Sublime-3-Änderungen http://sgaul.de/2014/03/27/guard-erkennt-keine-sublime-3-aenderungen/ http://sgaul.de/2014/03/27/guard-erkennt-keine-sublime-3-aenderungen/#comments Thu, 27 Mar 2014 15:30:36 +0000 http://sgaul.de/?p=2583 Weiterlesen ]]> Sublime Text 3 hat das Speichern von Dateien umgestellt, so dass Guard dieses nicht mehr als Änderung registriert. Abhilfe schafft hier eine Rückkehr zum alten Ansatz. Hierfür unter „Preferences“ > „Settings — User“ die folgende Option setzen:

{
  ...
  "atomic_save": false,
  ...
}
]]>
http://sgaul.de/2014/03/27/guard-erkennt-keine-sublime-3-aenderungen/feed/ 0
CanCanCan ist kein CanCan 2.0 http://sgaul.de/2014/03/25/cancancan-ist-kein-cancan-2-0/ http://sgaul.de/2014/03/25/cancancan-ist-kein-cancan-2-0/#comments Tue, 25 Mar 2014 21:11:22 +0000 http://sgaul.de/?p=2560 Weiterlesen ]]> Außerhalb meiner Wahrnehmung gibt es auf Github seit einiger Zeit ein Projekt, dass CanCan für tot erklärt und dessen Nachfolge antreten will: CanCanCan. 17 Menschen haben zum Projekt der „CanCanCommunity“ bereits beigetragen. So wird das kleine Gem von Ryan Bates, der seit längerer Zeit aufgrund gesundheitlicher Probleme ausfällt, weitergeführt. Wichtige Fixes für Rails 4 finden so ihren Weg ins Projekt.

Keine Entwicklung im CanCan-2.0-Branch

Mit CanCan hat man auch den 2.0-Branch geforkt, leider blieb dieser bisher unangetastet. Die neue Version sollte vor allem die von mir seit langem erwartete Unterstützung für Attribute mitbringen. So lässt sich nicht mehr nur definieren und prüfen ob ein Model insgesamt, sondern auch welche Attribute geändert werden dürfen:

can :update, :projects, [:name, :priority]

Der Verfasser der Readme vertröstet derweil:

I am currently focusing on the 1.x branch for the immediate future, making sure it is up to date as well as ensuring compatibility with Rails 4+. I will take a look into the 2.x branch and try to see what improvements, reorganizations and redesigns Ryan was attempting and go forward from there.

Bleibt zu hoffen, dass einer der Entwickler die Zeit für einen tieferen Blick findet.

]]>
http://sgaul.de/2014/03/25/cancancan-ist-kein-cancan-2-0/feed/ 0
Youtube für die Kommandozeile http://sgaul.de/2014/03/24/youtube-fuer-die-kommandozeile/ http://sgaul.de/2014/03/24/youtube-fuer-die-kommandozeile/#comments Mon, 24 Mar 2014 12:14:50 +0000 http://sgaul.de/?p=2571 Weiterlesen ]]> MPS-Youtube ist ein Python-Programm für die Kommandozeile, mit dem man komfortabel Musik und Videos von Youtube abspielen und in lokalen Playlists verwalten kann. Zudem lassen sich Videos oder ihr Audioanteil auf die Festplatte herunterladen.

Installation

Die Installation läuft über PIP, eine Art Paketmanager für Pythonmodule ähnlich Rubygems.

sudo apt-get install python-pip

MPS-Youtube setzt von Haus aus auf Mplayer. Das lässt sich ändern, ich bin jedoch den Weg des geringsten Widerstands gegangen:

sudo apt-get install mplayer

Nun kann das eigentliche Programm mittels PIP installiert werden. Es scheint sich hierbei um eine globale Installation zu handeln, Sudo-Rechte waren bei mir erforderlich:

sudo pip install mps-youtube

Nun kann man die vim-ähnliche Oberfläche mittels mpsyt starten.
mpsyt

Anwendung

Audio und Video abspielen

Wie Vim wird das Tool über eine unten angelegte Kommandozeile gesteuert. Ein Slash löst eine Suche nach einem Video, ein Doppelslash die Suche nach einer Youtube-Playlist aus:

/Suchbegriff nach Einzelvideo
//Suchbegriff nach Playlist

Man erhält eine nummerierte Liste mit Ergebnissen. Videos lassen sich durch Eingabe der Nummer abspielen, Playlists in gleicher Weise öffnen. Zudem lassen sich mehrere Videos nacheinander abspielen, etwa 1-10 oder auch 1,5,10.

Fehlende Videoausgabe

Bei mir ist das Programm nach der Installation als Musikwerkzeug ausgelegt, von Videos wird lediglich die Tonspur ausgegeben. Dies kann man temporär umgehen, indem man den abzuspielenden Videonummern die Optionen -f für Fullscreen bzw. -w für den Fenstermodus übergibt. Will man diese Einstellungen als Standard setzen, kann man dies über einen der entsprechenden Set-Befehle machen:

set show_video 1
set fullscreen 1

Playlists

Statt nur der Nummer kann man bspw. mittels add 1-10 oder add 1-10 [Playlistname] die zehn ersten Suchergebnisse in eine lokale Playlist legen. Diese kann man mit vp („view playlist“) öffnen und Inhalte wie Suchergebnisse abspielen. Mit ls lassen sich Playlists auflisten, mit play [Playlistname oder -ID] lassen sich ganze Playlists abspielen.

Insbesondere an dieser Stelle gibt es recht viele, aber auch intuitive Möglichkeiten mit benannten und temporären Listen zu hantieren. Die Github-Seite des Projekts gibt hier recht gute Einblicke. Dem Programm selbst kann man eine kleine Befehlsübersicht mittels help playlists entlocken.

Jukebox und Raspberry Pi

Das Programm hat mich recht positiv überrascht. Es ist klein, schnell und somit vor allem im Vergleich mit der doch immer aufwändiger werdenden Youtube-Seite eine echte Alternative. Mir kommen vor allem der Einsatz als Jukebox und Youtube-Client für den Raspberry Pi in den Sinn. Wer etwas in dieser Richtung sucht, sollte mal eine Blick darauf werfen.

Gotbletu hat das Programm in einem informativen Video zusammengefasst (englisch).

]]>
http://sgaul.de/2014/03/24/youtube-fuer-die-kommandozeile/feed/ 0
Rails 4 schützt Nutzer vor Regex-Schwachstellen http://sgaul.de/2014/03/19/rails-4-schuetzt-nutzer-vor-regex-schwachstellen/ http://sgaul.de/2014/03/19/rails-4-schuetzt-nutzer-vor-regex-schwachstellen/#comments Wed, 19 Mar 2014 19:30:23 +0000 http://sgaul.de/?p=2565 Weiterlesen ]]> Rails 4 warnt seine Nutzer vor einer weit verbreiteten Sicherheitslücke, die durch falsche Anwendung von regulären Ausdrücken auftritt. So sind Validatoren wie der folgende nicht mehr erlaubt:

validates :phone, format: { with: /^[0-9]+$/ }

Dies wirft einen Argument-Error wie diesen:

`check_options_validity': The provided regular expression
 is using multiline anchors (^ or $), which may present a
 security risk. Did you mean to use \A and \z, or forgot 
 to add the :multiline => true option? (ArgumentError)

Hintergrund ist, dass reguläre Ausdrücke in Ruby von Haus aus mehrere Zeilen matchen:

"0123456789" =~ /^[0-9]+$/
 => 0
 
 "0123456789<script>alert('42!');</script>" =~ /^[0-9]+$/
 => nil
 
 # aber:

 "0123456789\n<script>alert('42!');</script>" =~ /^[0-9]+$/
 => 0

Die Begrenzer ^ und $ matchen hier Zeilenanfang und -ende. In aller Regel will man jedoch Anfang und Ende des Gesamtstrings adressieren, wozu Ruby \A und \z bereitstellt:

"0123456789" =~ /\A[0-9]+\z/
 => 0 
 
 "0123456789\n<script>alert('42!');</script>" =~ /\A[0-9]+\z/
 => nil

Mit diesem Ausdruck läuft der obige Validator fehlerfrei durch.

Will man wirklich einzelne Zeilen validieren, kann man die obige Fehlermeldung mit der Option multiline: true deaktivieren.

]]>
http://sgaul.de/2014/03/19/rails-4-schuetzt-nutzer-vor-regex-schwachstellen/feed/ 0
“Rake aborted!” durch abweichende Version http://sgaul.de/2014/03/18/rake-aborted-durch-abweichende-version/ http://sgaul.de/2014/03/18/rake-aborted-durch-abweichende-version/#comments Tue, 18 Mar 2014 15:59:49 +0000 http://sgaul.de/?p=2561 Weiterlesen ]]> Stimmt die aktuell installierte Rake-Version nicht mit der im Gemfile.lock hinterlegten Version überein, bricht Rake mit einer Fehlermeldung ab:

rake routes
rake aborted!
You have already activated rake 10.1.1, but your Gemfile requires rake 10.1.0. Using bundle exec may solve this.


Stets bundle exec rake einzugeben ist recht müßig. Zwar ließe sich die Rake-Version des Projekts aktualisieren, in vielen Situationen kann man das jedoch nicht ohne weiteres entscheiden. Ein Downgrade zur Wunschversion ist jedoch recht einfach:

gem uninstall rake

Select gem to uninstall:
 1. rake-10.1.1
 2. rake-10.1.0
 3. All versions
> 1
Successfully uninstalled rake-10.1.1

Der Versionskonflikt ist somit behoben.

]]>
http://sgaul.de/2014/03/18/rake-aborted-durch-abweichende-version/feed/ 0
Wordpress-Plugin WP Inject: CC-Bilder einfügen http://sgaul.de/2014/03/13/wordpress-plugin-wp-inject-cc-bilder-einfuegen/ http://sgaul.de/2014/03/13/wordpress-plugin-wp-inject-cc-bilder-einfuegen/#comments Thu, 13 Mar 2014 21:28:33 +0000 http://sgaul.de/?p=2547 Weiterlesen ]]> Das Wordpress-Plugin WP Inject integriert eine Flicker-Suche in Wordpress, mit der man Bilder nach festgelegter Lizenz suchen, auf den Server kopieren, einbinden und die Quelle referenzieren kann.

Da ich in einem Staat mit dem Landgericht Köln lebe, habe ich den Spaß mal auf kommerzielle Nutzung ohne Zitatpflicht eingegrenzt. Leider bringt selbst die Suche nach „Wordpress“ dann keine brauchbaren Ergebnisse, aber die von mir gewählte Alternative wirkt doch auch jung und frisch.

Übrigens steht bei dem gewählten Bild auf Flicker, dass keine Urheberrechtsbeschränkungen bekannt sein. Tatsächlich steht in der Beschreibung, dass es zitiert werden muss. Blind vertrauen sollte man dem Plugin folglich nicht.

Vielen Dank an die Softwareperlen für den Hinweis auf das Plugin.

computer photo

Foto von Tyne & Wear Archives & Museums

]]>
http://sgaul.de/2014/03/13/wordpress-plugin-wp-inject-cc-bilder-einfuegen/feed/ 0
Methoden-Ketten: Rubys Object#tap und Datenkapselung http://sgaul.de/2014/03/09/methoden-ketten-rubys-objecttap-und-datenkapselung/ http://sgaul.de/2014/03/09/methoden-ketten-rubys-objecttap-und-datenkapselung/#comments Sun, 09 Mar 2014 17:36:40 +0000 http://sgaul.de/?p=2464 Weiterlesen ]]> Eine nette, mir bisher unbekannte Methode ist Rubys Object#tap, der man einen Block übergeben kann, in dem das Objekt als Argument zur Verfügung steht. Klingt im ersten Moment vielleicht unnötig, kann aber insbesondere in Views für etwas schöneren Code sorgen. Ein kleines Negativbeispiel in HAML:

%table
  %tbody
    %tr
      %td= current_company.name
      %td= current_company.owner ? "#{current_company.owner.first_name} #{current_company.owner.last_name}" : t('.unknown_owner')

Object#tap

Die ständige Wiederholung von current_company.owner streckt den Code. Gäbe es mehrere Besitzer, würde ein current_company.owners.each do |owner| Abhilfe schaffen und den Zugriff owner im folgenden Block bereitstellen. Tap macht genau das selbe, nur dass es auf jedem Objekt, also auch Nicht-Kollektionen funktioniert:

%table
  %tbody
    %tr
      %td= current_company.name
      - current_company.owner.tap do |owner|
        %td= owner ? "#{owner.first_name} #{owner.last_name}" : t('.unknown_owner')

Eine gute Zeile im Sinne der Lesbarkeit.

Information Hiding

Genau genommen ist es Glück, dass die Code-Stelle selbst mit dieser Änderung etwas unsauber aussieht. Sie zeigt uns ein Problem mit der Datenkapselung. Ein View für ein Unternehmen sollte nicht wissen müssen, aus welchen Details sich ein Darstellungsname des Besitzers ergibt. Hierfür bietet Rails die (an dieser Stelle etwas zu umständlichen) Partials oder Helper: display_owner(owner).

Decorators: Draper und Co.

Nun sind Helper für den objektorientierten Entwickler eine hässliche Sache, da sie auf Modulen basieren und nur eine Sammlung view-globaler Methoden darstellen. Einen besseren Ansatz bieten Decorators, die es für Rails etwa in Form von Draper gibt. Ist ein Owner dekoriert, ruft man im View z.B. owner.display_name oder gar owner auf. Für letzteres muss lediglich die Methode to_s des Dekorators überschrieben werden:

class OwnerDecorator < Draper::Decorator

  decorates :owner
  delegate_all
  include Draper::LazyHelpers

  def to_s
    if owner
      link_to "#{first_name} #{last_name}", owner_path(owner)
    else
      ""
    end
  end

end

Somit lässt sich an jeder Stelle denkbar einfach ein nullsicherer Link zu einem Besitzer einbinden. Auch Rechteverwaltung kann an dieser Stelle eingebracht werden, da der Dekorator Zugriff auf alle Helper und den Controller hat. Zudem lässt sich das ganze noch isoliert testen.

Sind die Dekoratoren korrekt konfiguriert, sieht das eingangs vorgestellte Problem nicht mehr wie eines aus:

%table
  %tbody
    %tr
      %td= current_company.name
      %td= current_company.owner
]]>
http://sgaul.de/2014/03/09/methoden-ketten-rubys-objecttap-und-datenkapselung/feed/ 0
RVM: Gemset erstellen und .versions.conf anlegen http://sgaul.de/2014/03/09/rvm-gemset-erstellen-und-automatisch-konfigurieren/ http://sgaul.de/2014/03/09/rvm-gemset-erstellen-und-automatisch-konfigurieren/#comments Sun, 09 Mar 2014 14:21:57 +0000 http://sgaul.de/?p=2473 Memo an mich selbst: Ein neues Gemset einrichten und mittels .versions.conf als Standard für das aktuelle Verzeichnis festlegen:

rvm use --create --versions-conf ruby-2.1@gemset_name

In aller Regel benötige ich auch Bundler:

gem install bundler
]]>
http://sgaul.de/2014/03/09/rvm-gemset-erstellen-und-automatisch-konfigurieren/feed/ 0
Git-Commit-Messages aus Redmine-Issues http://sgaul.de/2014/03/07/git-commit-messages-aus-redmine-issues/ http://sgaul.de/2014/03/07/git-commit-messages-aus-redmine-issues/#comments Fri, 07 Mar 2014 17:10:20 +0000 http://sgaul.de/?p=2462 Weiterlesen ]]> Ein nerviges Prozedere ist das Suchen nach Issue-Nummern, wenn ich via Git eine Änderung commiten möchte. Git kann hier helfen und bietet mit seinen Hooks die Möglichkeit eigene Skripts in allen denkbaren Situationen auszulösen. Hierzu zählt auch die Modifikation der Commit-Message-Vorlage, die Git mit Status-Kommentaren füllt:

 
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# On branch new
# Changes to be committed:
#   (use "git reset HEAD ..." to unstage)
#
#       modified:   zeus.json
#


Hier hätte ich gern eine Liste der Issues, an denen ich aktuell arbeite. Hierfür muss man in Redmine die Rest-Authentification aktivieren und kann dann im Git-Projekt unter .git/hooks/prepare-commit-msg den folgenden Hook hinterlegen:

#!/usr/bin/env python

import sys, socket, urllib2, json

url = 'http://myredmine/issues.json?key=abcd&project_id=myproject&assigned_to_id=me'
socket.setdefaulttimeout(3)

try:
	originalfile = open(sys.argv[1], "r")
	original = originalfile.read()
	originalfile.close()
	messagefile = open(sys.argv[1], "w")
	data = json.load(urllib2.urlopen(url))
	messagefile.write('\n\n')
	for issue in data["issues"]:
		messagefile.write(('# Issue #' + str(issue["id"]) + ' ' + issue["subject"] + '\n').encode('utf-8'))
	if not original.splitlines()[0]:
		messagefile.write(original)
	messagefile.close()
except:
	print "Failed to get issues from Redmine."

Leider habe ich keine wirkliche Erfahrung mit Python, aber immerhin macht das Skript seinen Job: Es lädt mittels Redmine-API die mir zugewiesenen, offenen Tickets herunter und zeigt Nummer und Titel als Kommentare an.

# Issue #2 Find a new issue #1
# Issue #1 Microsoft has a majority market share

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# On branch new
# Changes to be committed:
#   (use "git reset HEAD ..." to unstage)
#
#       modified:   zeus.json
#

Nachtrag

Die Commit-Message sollte nur verändert werden, wenn die erste Zeile noch frei ist. Andernfalls kann es zu Problemen mit anderen Programmen wie Git Cola kommen, so dass die Issue-Nummer vor der Commit-Nachricht eingefügt und als Teil dieser Nachricht angesehen wird.

]]>
http://sgaul.de/2014/03/07/git-commit-messages-aus-redmine-issues/feed/ 0