GitExtensionsで学ぶgit入門 (4) メイン画面の見方

今回はメイン画面の見方を簡単に説明しようと思います。

※最初に断っておきますが、今回のエントリは、git初心者の方にとっては多くの「?」を残すと思います。
 主要な単語を頭の隅にとどめておく程度で良いので、さらっと流し読みしていただけたらと思います。

下図は適当にコミット、分岐、マージを重ねた作業ツリーを持っている状態の画面です。

実際にはツリーがここまでカオスになる前にコミットを整理したりするのが一般的だと思いますが、今回はあくまでも例ということで。
よく見る、使う箇所に番号を振ってみましたので、順に軽く触れてみます。

1:現在開いているリポジトリのパス。ここから別のリポジトリを開くこともできます。
2:現在選択しているブランチ(変更の分岐点)。別のブランチを選択もできます。
3:コミットボタン。リポジトリ内の変更を記録するときに使います。詳しくは次回。
4:タグ(青い四角)、ブランチ(赤い四角)、コミットメッセージ(1行のみ)、コミッター、コミット時間などのコミットログを表示します。
5:ブランチ。作業の分岐点に作成することで、ソースを分岐させることができます。
  現在選択中のブランチには図のように三角矢印がつきます。*1
6:作業ツリー。下から順に並び、最新のコミット情報が上に来ます。
  赤青緑の色が付いている線は、現在編集中のソースに影響があるツリーをトレースしています。
  逆に灰色の線はソースに影響のない歴史です。ブランチの選択を切り替えることで、どの歴史が関係するかを一目で把握できます。
7:タグ。ブランチと似ていますが、静的なマークで、移動などができません。
  新バージョンのリリース時などにタグをつけることで、マイルストーン的な役割を果たします。
8:コミットタブ。コミット日付やコミットのハッシュ値*2、コミットメッセージの全文、
  所属ブランチ、所属タグを表示します。
9:画面の上半分で選択している、現在の行の歴史で管理されているファイルを、ツリー形式で表示します。
  タブ内の右ペインは、ペイン左のツリーで選択したファイルの内容を表示しています。
 
10:画面の上半分で選択している、現在の行の歴史でコミットされたファイルを表示します。
  タブ内の右ペインは、ペイン左で選択したファイルにおける、前回のコミットからの変更点を表示します。
  赤い行が削除された箇所。緑の行が追加された箇所を示しています。
  また、差分は全ソースを表示していません。灰色の行はソースの行数を表示していますので、実際のファイルはその数字を参考に行を特定します
 

ざくっと文章で書いているため、理解し難い内容となって申し訳ありませんが、これからのエントリで説明していきます。

私の会社で運用を始めて、序盤で多くの人達がハマっていたのが、
「コミットすることでどのようにファイルが書き変わっていくのか」ということと、
「ブランチの捉え方」でした。
おそらくgitを初めて触る方は、ここでつまづくことが多いのではないかと思いますので、
今後数回に分けて、コミットとブランチについて説明したいと思います。

*1:ブランチを選択状態にすることを「チェックアウト」と言います。Visual source safeやTeam Foundation Serverのそれとは全く違う機能です。

*2:コミットを一意に認識するための値

GitExtensionsで学ぶgit入門 (3) 初めてのコミット

前回gitignoreの登録まで完了しましたので、いよいよリポジトリに歴史を登録(コミット)していきます。
コミットについての詳細は、これから先のエントリで触れたいと思いますので、その時に解説します。
今回はコミットの流れについて理解するにとどめておきますので、基本的な部分を理解してください。

gitignore登録後、前回同様下記画面に戻って来ます。
今度は「コミット」ボタンをクリックしてください。


下図のような画面が表示されると思います。
ペインの構成はオレンジの枠に解説しています。


これだけではわかりにくいと思いますので、基本的なコミットの流れを説明します。

1:まず左上のペインに表示されている追加されたファイルですが、まだコミット対象になっていませんので、
 「↓」ボタンをクリックし、コミット対象にします(この作業を「ステージング」と言います)
 「↓」ボタンは選択した1ファイルをステージングし、「↓↓」ボタンは検知した変更を全てステージングします。
 また、ステージされたファイルをコミット対象外にすることをアンステージングと言い、
 「↓」ボタンの左側にある「↑」ボタンでアンステージを行います。

2:次に、コミットメッセージを入力します。GitExtensionsではコミットメッセージの入力は強制となっているようで、
 ここを入力せずにコミットできないようです。コミットメッセージを適当に書くと、そのコミットでどんな変更が行われたのか追跡しにくいので、
 あとから見てもわかりやすい内容を記入しましょう

3:ステージングとコミットメッセージの入力が完了したらコミットボタンをクリックしてください。
 コミットボタンを押す直前の状態はこんな感じです。


コミットボタンをクリックすると、進捗ダイアログが表示されます。
これは実際に発行したコマンドと、その結果を表示しています。
コマンドが通れば「緑のチェックマーク」、失敗すると「赤い×」が表示されますので、
失敗した場合はログをよく読んでみると解決策がわかるかもしれません。

「ダイアログを閉じない」のチェックを外すと、コマンドの実行が成功した場合、自動でダイアログを閉じることができます。
失敗した時だけログを見られればいいならば、チェックを外して下さい。

コミットが完了すると、GitExtensionsのメイン画面に戻ってきます。
今度は上半分の表示が変わっていますが、これは先ほどと違い、コミットされた歴史ができたためです。


次回はこのメイン画面の見方を説明します。

GitExtensionsで学ぶgit入門 (2) バージョン管理したくないファイルを定義する

バックアップファイルやテンポラリファイルのように、「フォルダ内に置きたいけど、バージョン管理してほしくないファイル」があったとします。
そういう時は、「.gitignore」ファイルにそのパターンを記述しておきましょう。
.gitignoreに登録されたパターンにマッチするファイルは、内容が変更されても検出されず、
バージョン管理の対象外となります。

前回リポジトリを初期化しましたが、作成が完了するとそのまま下記画面に進んだと思います。
この画面に「.gitignoreの編集」ボタンがありますね。
これを利用してみましょう。


「.gitignoreの編集」ボタンをクリックすると、下記画面が開きます。
何やら右側に書かれていますが、これは左のエディタ部分に記入する内容の例です。
この例は「デフォルトの無視ファイル」ボタンをクリックすると、左のエディタに送られますので、
VisualStudioで開発している人は、無視パターンをほとんど気にすることなく登録を完了することが出来ます。ラッキーですね!


しかしVisualStudioを使われていない方は、残念ながら手動でパターンを登録していかないといけません。
左のエディタにガリガリと記入してもいいのですが、せっかくなので入力補助機能を使ってみましょう。

このエントリでは、フォルダの構成が下記のようになっていると仮定して説明します。

入門リポジトリ
├ .git
├ フォルダだよん                                                                                                                                                                                                                                                                                    < Dir >
│ ├ dllだよん.dll
│ └ ファイルだよん.txt
├ source.txt
└ 管理させたくない.dll

「Add pattern」ボタンをクリックしてください。
すると下記画面が開きます。


この画面は、先ほどのgitignore編集ダイアログの右側にあるような書式(*.obj等)を「無視するファイルパターン」に入力すると、
リポジトリの管理対象となるファイルから、パターンにマッチするファイルの一覧をPreview欄に表示してくれます。
つまり、Preview欄に表示されているファイルがリポジトリの管理対象外となります。
上記の例では「*.dll」という文字に「管理させたくない.dll」がマッチしたので、
これを登録すると「管理させたくない.dll」が管理対象外になることになります。
登録は「.gitignoreに」ボタンを押すと追加できます。

また、下記のようにフォルダ名を指定すると、フォルダ内のファイル全てが無視されます。


これを繰り返し、全て登録完了したら、編集画面の「保存」ボタンをクリックしてください。
今回の例では.gitignoreも無視して、source.txt以外は管理しないようにしました。

gitignoreの書式

gitignoreの書式は下記のようなルールがあります。

  • #で始まる行はコメントとして扱う。
  • !で始まる行は、一致するパターンを無視しない
  • /で終わる行は、一致するパターンをフォルダのみに適用する
  • /を含む行は、/を相対フォルダの区切りとする
  • それ以外はGLOB*1でパターンマッチングする

上記を元に、色々試してみてください。

後からgitignoreを追記したい場合

今まで説明した操作は、まだ初回のコミットを済ませる前にしか表示されません。
もし後からgitignoreに追加したいパターンが増えた場合は、メイン画面から、
「コミットボタン>作業ディレクトリの変更点>無視されたファイルを編集する」(下図)
でgitignore編集ダイアログを開くことができますので、そこからパターンを追加してください。

また、.gitignoreファイルは、.gitフォルダが作られた場所に作られますので、直接gitignoreファイルをテキストエディタで開いて編集も可能です。


これでソース管理の準備が全て整いました。
次回はソースをコミットし、リポジトリに最初の歴史記録したいと思います。

*1: "unix glob"等で検索すると参考文献が見つかると思います。私はunixに明るくないので詳しくは知りませんが、正規表現に近いので適当でも何とかなります。

GitExtensionsで学ぶgit入門 (1) リポジトリを作成する

バージョン管理システムは「リポジトリ」と呼ばれるデータベースで、ファイル群の状態を管理しています。
今回はリポジトリを作成し、ファイルのバージョンを管理する準備をしていきます。

では早速GitExtensionsを起動しましょう。
起動すると下のような画面が表示されますので、「リポジトリの作成」をクリックします。


リポジトリの作成ダイアログが開きますので、バージョン管理したいフォルダを参照し、作成ボタンをクリックします。


無事作成が成功すると、下記メッセージが表示されます。
「空のGitリポジトリを初期化した」とのメッセージの通り、この時点ではリポジトリを初期化しただけで、
まだバージョン管理するファイルは登録(コミット)されていません。
バージョン管理の準備ができた程度にとらえるとよいでしょう。


初期化が完了すると、指定したフォルダに「.git」という名の隠しフォルダが作成されます。
(下図の.git以外のファイルは、すでに開発中のソース等と考えてください。)


基本的にgitは、リポジトリを作成したフォルダ以下にあるファイルのバージョンを管理します。*1
.gitを作成する場所は、管理したい階層のトップフォルダにするとよいでしょう。
次回はバージョン管理するファイル、しないファイルの設定を行っていきます。

*1:ファイル自体を管理せずに、バージョン管理だけを行うbareリポジトリと言うのもありますが、リポジトリを分散管理する時に使うものなので、ここでは触れません。

GitExtensionsで学ぶgit入門 (0) インデックス

git入門サイトは数多くありますが、意外とGUIクライアントの使い方は探してもないものですね。
というわけで私自身の備忘録を兼ねて、お気に入りツールGitExtensionsを使った
「GitExtensions入門」をまとめてみようかと思います。

対象読者

  • gitを使ってみたいけど、CUIが嫌いで敬遠していた方

前提条件

  • Unicode対応版のmsysGit(msysGit v1.7.10以降)*1をインストール済みであること
  • GitExtensions*2をインストール済みであること
  • KDiff3(マージツール)をインストール済みであること*3
    • 必ずしもKDiff3をインストールする必要はありませんが、ここではKDiff3を使ってマージを解説していきます。

 ※GitExtensionsのインストールに関する情報は、ネットを検索したらすぐに見つかると思いますので、ここでは触れません。
 ※一連のエントリで解説するGitExtensionsのバージョンは、現時点での最新バージョンである2.31を使用して解説していきます。

目次

ローカルでバージョン管理する

(以下エントリに合わせて追加していきます)

INotifyPropertyChanged的なUndoの実装を試みる(まとめてUndo編)

4度目のINotifyなUndoについての記事です。

今回は、異なるクラス間の複数のプロパティを一括でUndoする処理について考えたいと思います。

この機能は、レコード更新などの、1回の処理で複数のプロパティーを変更したときなどに必要になると思います。

では早速拡張していきましょう。

まず最初に、複数のプロパティーの変更をどうコマンド化するかについて考えてみます。
単純に考えて、UndoPointManagerのUndoメソッドを実行したときに、
「一括で書き換えたプロパティを巻き戻す」という処理を、
1つのコマンドオブジェクトにしてしまえばよいかと思います。

ということで、「コマンドオブジェクトをためて、一気に実行できるコマンドオブジェクト」
を作成すればいいと考えました。
要は「コマンドを入れ子にしておけるコマンド」ですね。

というわけでこんなクラスを作成してみました。

ReservableUndoRedoCommand
  public class ReservableUndoRedoCommand : IUndoRedoCommand {
    #region メンバー変数
    List<IUndoRedoCommand> list = new List<IUndoRedoCommand>();
    #endregion

    #region プロパティー
    public int Count { get { return list.Count; } }
    #endregion

    #region パブリックメソッド
    public void Add(IUndoRedoCommand commnad) {
      list.Add(commnad);
    }

    public void Undo() {
      for (int i = list.Count - 1; i >= 0; i--) {
        list[i].Undo();
      }
    }

    public void Redo() {
      foreach (var command in list) {
        command.Redo();
      }
    }
    #endregion
  }

Addメソッドで追加したコマンドを順に内部リストに追加し、
Undo時には追加した降順でコマンドを実行、Redo時には昇順でコマンドを実行するというシンプルな設計です。
これをUndoPointManagerに食わせてやれば、あとはそちらで勝手に実行してくれるはずです。

さて、ここで問題になるのは、いつReservableUndoRedoCommandを生成するかということです。
前回までのコードでは、UndoするためのPropertyChangeCommandは、プロパティーを変更するたびに、
イベントをハンドルして生成していました。
ReservableUndoRedoCommand生成のタイミングはここしかないですね。
しかしCreatedUndoPointでは、1つのコマンドしか持てないため、
コマンドをキャッシュするためにReservableUndoRedoCommandをメンバー変数に定義しておかなければいけませんね。
あとはどちらのコマンドを返すかは、コマンド蓄積中かどうかをifで分岐させます。

UndoPointManager.CreatedUndoPointに追加するコード
    /// <summary>
    /// コマンドを蓄積中か
    /// </summary>
    private bool isReserving = false;
    /// <summary>
    /// 蓄積中のコマンド
    /// </summary>
    private ReservableUndoRedoCommand reservingCommand = new ReservableUndoRedoCommand();
    
    /// <summary>
    /// コマンドを蓄積中か
    /// </summary>
    public bool IsReserving {
      get {
        return isReserving;
      }
      set {
        if (value == false) {
          if (reservingCommand.Count > 0) {
            redoStack.Clear();
            undoStack.Push(reservingCommand);
            reservingCommand = new ReservableUndoRedoCommand();
          }
        }
        isReserving = value;
     }
    }
    
    private void CreatedUndoPoint(object sender, UndoPointCreatedArgs e) {
    //redoStack.Clear();
    //undoStack.Push(e.Command);
    //↓
      if (isReserving) {
        reservingCommand.Add(e.Command);
      }
      else {
        redoStack.Clear();
        undoStack.Push(e.Command);
      }
    }

こんな感じの実装になるかと思います。

注目すべきはIsReservingプロパティーです。
コマンドを蓄積するかどうかは、Managerの外部による変更の仕方によって決められるため、
プロパティーとして公開する必要があります。
そして、IsReserving=trueの(コマンドを蓄積する)間はUndoPointを作らず、
ひたすらReservableUndoRedoCommandにコマンドを蓄積していきます。
そしてIsReserving=false(コマンドを蓄積しない)とした瞬間に、
蓄積したコマンドをUndoPointとしてスタックさせる、という仕組みです。
UndoPointを生成したら、ReservableUndoRedoCommandをnewして、次の蓄積に備えることも忘れないようにしておきます。

さて、複数のプロパティーの変更をまとめて管理できているか、サンプルを組んでみましょう。

    [STAThread]
    static void Main() {
      var manager = new UndoPointManager();
      var data1 = new ConcreteData();
      var data2 = new ConcreteData();
      manager.Add(data1);
      manager.Add(data2);

      Console.WriteLine("data1:{0}\tdata2:{1}", data1.Name, data2.Name);
      
      manager.IsReserving = true;
      data1.Name = "あいう";
      data2.Name = "山田太郎";
      manager.IsReserving = false;

      Console.WriteLine("data1:{0}\tdata2:{1}", data1.Name, data2.Name);
      manager.Undo();
      Console.WriteLine("data1:{0}\tdata2:{1}", data1.Name, data2.Name);
      manager.Redo();
      Console.WriteLine("data1:{0}\tdata2:{1}", data1.Name, data2.Name);
    }

出力:

data1:		data2:
data1:あいう	data2:山田太郎
data1:		data2:
data1:あいう	data2:山田太郎

きちんと動いてるみたいです。
manager.IsReservingを都度切り替えなければいけないのでちょっと煩わしいですが、
うまくメソッド化できればそんなに気にもならないかなと…(^^;

駆け足で説明した感が否めないのですが、実装したコード+Undoボタンの有効状態をバインディングさせた
VS2010のサンプルを公開して締めたいと思います。論よりソース!(ひどい言い訳)
MITライセンスにて提供します。例外処理はしてないのでご利用は計画的に。
DatabindingUndoSample
※SkyDriveで公開していますが、使い慣れてないので何かミスしてましたらご連絡いただければと思います。

INotifyPropertyChanged的なUndoの実装を試みる(データバインディング編)

またまたINotifyなUndoについてです。
今回は実際にWPFのデータバインディングを利用し、GUIからUndoしてみたいと思います。

前回データの実体としてConcreteDataを作成しましたが、データバインディングを実装するために、
こいつにINotifyPropertyChangedを実装しましょう。

  public class ConcreteData : INotifyUndoPointCreated, INotifyPropertyChanged {
  ・
  ・
  ・

INotifyPropertyChangedに定義されているイベントも、ConcreteDataに下記のように実装します。

    public event PropertyChangedEventHandler PropertyChanged;
    
    private void OnPropertyChanged(string propertyName) {
      if (PropertyChanged != null) {
        var args = new PropertyChangedEventArgs(propertyName);
        PropertyChanged(this, args);
      }
    }

ここまでは通常のデータバインディングの流れと同じだと思います。
さて、いつものデータバインディングならば、プロパティーのSetterにこのイベントを起動するコードを書く必要がありますが、
間にUndo(コマンドオブジェクト)が介入すると、ちょっと気を使う必要が出てきます。
というのは、PropertyChangedイベントは、データを書き換えた時だけではなく、Undoした時にも発生させなければ、当然Viewにはその変更が通知されません。
なので、ConcreteDataのNameプロパティを例にすると、下記のようなコードを追加することになります。

    public string Name {
      get {
        return _name;
      }
      set {
      //PropertySetter<string>(x => this._name = x, _name, value);
        PropertySetter<string>(x => { this._name = x; OnPropertyChanged("Name"); }, _name, value);
      }
    }

はい、一気に泥臭さがUPしました。
しかしカプセル化したコマンド内にイベントの起動処理を書かないとどうしようもないので、
これは仕方がない事なのかなぁ。
まぁここで時間を取るのもなんなので、先に進みたいと思います。

と言っても、Modelの拡張はこれで終わりです。
あとはView側にデータをバインドさせれば完成です。
(一連のエントリーをまとめたらソースを公開したいと思いますので、XAMLの記述等ははしょらせていただきます。)
XAML内のTextBoxの属性にターゲットプロパティを指定し、(Text="{Binding Path= Name}"等)
下記のような感じでコードを実装します。

  public partial class MainWindow : Window {
    #region メンバー変数
    private ConcreteData data = new ConcreteData();
    private UndoPointManager manager = new UndoPointManager();
    #endregion

    #region コンストラクター
    public MainWindow() {
      InitializeComponent();

      dataNameText.DataContext = data;
      dataNumberText.DataContext = data;

      manager.Add(data1);
    }
    #endregion

    #region イベント
    private void UndoButtonClicked(object sender, System.Windows.RoutedEventArgs e) {
      manager.Undo();
    }
    private void RedoButtonClicked(object sender, System.Windows.RoutedEventArgs e) {
      manager.Redo();
    }
    #endregion
  }

通常のデータバインディングと異なる点は、
コンストラクターでUndoPointManagerにデータをAddしている事と、
Undoしたいとき(Undoボタンクリック時)にUndoPointManager.Undo()を呼んでいる事だけです。
これだけで、割といい感じにUndoされてるのがうれしいですね。

次回は複数のプロパティの変更をまとめてUndoする仕組みについて考えたいと思います。