Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 12996] ファイルの概要

このコミットは、Goプロジェクトのコードレビューダッシュボードシステムにおいて、コードレビューのスレッドメールのMessage-IDを記録するように変更を加えるものです。これにより、将来的に「R=...」形式の返信メールを適切にスレッド化(関連付ける)できるようになるための基盤を構築します。具体的には、CL構造体にLastMessageIDフィールドを追加し、受信したメールのMessage-IDをデータストアに保存するロジックが導入されています。

コミット

commit 1bdb788b2ea5147ff7847f7a401a9da994a5e360
Author: David Symonds <dsymonds@golang.org>
Date:   Mon Apr 30 22:47:51 2012 +1000

    misc/dashboard/codereview: record Message-ID of code review thread mails.
    
    This will allow us to properly thread "R=..." mails at a later time.
    
    R=golang-dev, r
    CC=golang-dev
    https://golang.org/cl/6135053

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/1bdb788b2ea5147ff7847f7a401a9da994a5e360

元コミット内容

misc/dashboard/codereview: record Message-ID of code review thread mails.

This will allow us to properly thread "R=..." mails at a later time.

R=golang-dev, r
CC=golang-dev
https://golang.org/cl/6135053

変更の背景

この変更の背景には、コードレビュープロセスにおけるメール通知の管理と、それらのメールを論理的な会話スレッドとして適切に扱う必要性があります。Goプロジェクトでは、Rietveld(Googleが開発したコードレビューツール)をベースとしたシステムが使用されており、コードレビューのコメントやステータス変更はメールで通知されます。

従来のシステムでは、これらのメールが必ずしも適切にスレッド化されていなかった可能性があります。特に、レビュー担当者からの「R=...」(Reviewed=...)といった形式の返信メールは、元のレビューコメントや変更リスト(CL: Change List)と関連付けられず、独立したメールとして扱われてしまうことが問題でした。

メールのスレッド化は、関連するメールをグループ化して表示することで、ユーザーが特定の話題やコードレビューの進捗を追いやすくするために非常に重要です。これを実現するためには、メールヘッダのMessage-IDIn-Reply-Toフィールドが鍵となります。Message-IDは各メールに一意に割り当てられる識別子であり、In-Reply-Toは返信元のメールのMessage-IDを指します。これにより、メールクライアントは関連するメールをスレッドとして表示できます。

このコミットは、将来的にIn-Reply-Toヘッダを適切に設定できるようにするための第一歩として、まずコードレビュー関連のメールのMessage-IDをシステム側で記録・保存することを目的としています。これにより、後続の変更でappengine/mailパッケージがIn-Reply-Toヘッダの設定をサポートするようになった際に、スムーズにスレッド化機能を実現できるようになります。

前提知識の解説

1. Rietveld (コードレビューシステム)

Rietveldは、Googleが開発したWebベースのコードレビューシステムです。PerforceやGitなどのバージョン管理システムと連携し、変更差分(diff)の表示、コメントの追加、レビューの承認(LGTM: Looks Good To Me)などの機能を提供します。Goプロジェクトのコードレビューシステムは、このRietveldをベースに構築されています。レビューの通知は通常、メールで行われます。

2. Google App Engine (GAE)

Google App Engineは、Googleが提供するPaaS(Platform as a Service)です。開発者はインフラの管理を気にすることなく、アプリケーションをデプロイ・実行できます。Goプロジェクトのコードレビューダッシュボードは、Google App Engine上で動作していると考えられます。GAEは、データストア(NoSQLデータベース)やメール送信サービスなど、様々なAPIを提供しています。

3. appengine/datastore

Google App Engineのデータストアは、スケーラブルなNoSQLデータベースサービスです。Go言語からはappengine/datastoreパッケージを通じてアクセスできます。データは「エンティティ」として保存され、各エンティティは「種類(Kind)」と一意の「キー」を持ちます。このコミットでは、CL(Change List)エンティティに新しいフィールドを追加し、その値を保存するためにデータストアが利用されています。

4. Message-IDIn-Reply-To (メールヘッダ)

  • Message-ID: 各メールに一意に割り当てられる識別子です。通常、<一意な文字列@ドメイン>のような形式をしています。メールクライアントやMTA(Mail Transfer Agent)によって自動的に生成されます。
  • In-Reply-To: このメールがどのメールへの返信であるかを示すヘッダです。返信元のメールのMessage-IDがここに記述されます。
  • References: In-Reply-Toと同様にスレッド化に利用されますが、こちらはスレッド内の過去のすべてのMessage-IDをリストします。

これらのヘッダを適切に利用することで、メールクライアントは関連するメールをグループ化し、会話の流れを追跡しやすくします。

5. 「R=...」メール

Goプロジェクトのコードレビューでは、レビュー担当者が変更を承認した際に「R=...」という形式のコメントをメールで送ることがあります。これは「Reviewed by ...」を意味し、レビューが完了したことを示します。このコミットの目的は、これらのメールが元のコードレビューのスレッドに正しく関連付けられるようにすることです。

技術的詳細

このコミットは、Goプロジェクトのコードレビューダッシュボードのバックエンドにおけるデータモデルとメール処理ロジックに焦点を当てています。

1. CL構造体へのLastMessageIDの追加

misc/dashboard/codereview/dashboard/cl.goファイルにおいて、CL構造体(Change List、つまりコードレビュー対象の変更セットを表すデータモデル)にLastMessageIDという新しいフィールドが追加されました。

type CL struct {
    // ... 既存のフィールド ...
    // Mail information.
    Subject       string   `datastore:",noindex"`
    Recipients    []string `datastore:",noindex"`
    LastMessageID string   `datastore:",noindex"` // <-- 追加
    // ... 既存のフィールド ...
}

このフィールドは、そのCLに関連する最新のメールのMessage-IDを保存するために使用されます。datastore:",noindex"タグは、このフィールドがデータストアのインデックス作成の対象外であることを示しており、クエリのパフォーマンスには影響せず、単に値を保存する目的であることを意味します。

2. LastMessageIDの保存ロジック

misc/dashboard/codereview/dashboard/mail.goファイルにおいて、受信したメールを処理するhandleMail関数内で、メールのMessage-IDを対応するCLエンティティのLastMessageIDフィールドに保存するロジックが追加されました。

func handleMail(w http.ResponseWriter, r *http.Request) {
    // ... 既存の処理 ...

    // Track the MessageID.
    key := datastore.NewKey(c, "CL", m[1], 0, nil)
    err = datastore.RunInTransaction(c, func(c appengine.Context) error {
        cl := new(CL)
        err := datastore.Get(c, key, cl)
        if err != nil && err != datastore.ErrNoSuchEntity {
            return err
        }
        cl.LastMessageID = msg.Header.Get("Message-ID") // <-- ここでMessage-IDを取得し設定
        _, err = datastore.Put(c, key, cl)
        return err
    }, nil)
    if err != nil {
        c.Errorf("datastore transaction failed: %v", err)
    }

    // ... 既存の処理 ...
}

この処理はトランザクション内で行われます。これは、データストアへの書き込みがアトミックに行われることを保証するためです。つまり、CLエンティティの取得、LastMessageIDの更新、そしてエンティティの保存が、すべて成功するか、すべて失敗するかのいずれかになります。これにより、データの整合性が保たれます。

msg.Header.Get("Message-ID")は、受信したメールのヘッダからMessage-IDフィールドの値を取得しています。

3. LastMessageIDの永続化

misc/dashboard/codereview/dashboard/cl.goupdateCL関数では、CLエンティティを更新する際に、既存のLastMessageIDが失われないようにする変更が加えられました。

func updateCL(c appengine.Context, n string) error {
    // ... 既存の処理 ...
    } else if err == nil {
        // LastMessageID and Reviewer need preserving.
        cl.LastMessageID = ocl.LastMessageID // <-- 既存のLastMessageIDを保持
        cl.Reviewer = ocl.Reviewer
    }
    // ... 既存の処理 ...
}

これは、CLエンティティがデータストアから読み込まれ、何らかの更新が行われる際に、LastMessageIDフィールドが誤って上書きされたり、空になったりするのを防ぐためのものです。これにより、一度保存されたLastMessageIDが、その後のCLの更新プロセスで永続的に保持されることが保証されます。

4. 将来の機能拡張への言及

misc/dashboard/codereview/dashboard/cl.gohandleAssign関数には、将来的な機能拡張に関するコメントが追加されています。

// TODO(dsymonds): Use cl.LastMessageID as the In-Reply-To header
// when the appengine/mail package supports that.

このコメントは、このコミットがMessage-IDを記録する基盤を構築するものであり、実際にメールのスレッド化を実現するためには、Google App Engineのメール送信API(appengine/mail)がIn-Reply-Toヘッダの設定をサポートする必要があることを示唆しています。このコミット時点ではその機能が利用できなかったため、将来の課題として残されています。

コアとなるコードの変更箇所

misc/dashboard/codereview/dashboard/cl.go

--- a/misc/dashboard/codereview/dashboard/cl.go
+++ b/misc/dashboard/codereview/dashboard/cl.go
@@ -45,8 +45,9 @@ type CL struct {
 	LGTMs       []string
 
 	// Mail information.
-	Subject    string   `datastore:",noindex"`
-	Recipients []string `datastore:",noindex"`
+	Subject       string   `datastore:",noindex"`
+	Recipients    []string `datastore:",noindex"`
+	LastMessageID string   `datastore:",noindex"`
 
 	// These are person IDs (e.g. "rsc"); they may be empty
 	Author   string
@@ -193,6 +194,8 @@ func handleAssign(w http.ResponseWriter, r *http.Request) {
 					Subject: cl.Subject + " (issue " + n + ")",
 					Body:    "R=" + rev + "\n\n(sent by gocodereview)",
 				}
+				// TODO(dsymonds): Use cl.LastMessageID as the In-Reply-To header
+				// when the appengine/mail package supports that.
 				sendMailLater.Call(c, msg)
 			}
 		}
@@ -339,7 +342,8 @@ func updateCL(c appengine.Context, n string) error {\
 		if err != nil && err != datastore.ErrNoSuchEntity {
 			return err
 		} else if err == nil {
-			// Reviewer is the only field that needs preserving.
+			// LastMessageID and Reviewer need preserving.
+			cl.LastMessageID = ocl.LastMessageID
 			cl.Reviewer = ocl.Reviewer
 		}
 		_, err = datastore.Put(c, key, cl)

misc/dashboard/codereview/dashboard/mail.go

--- a/misc/dashboard/codereview/dashboard/mail.go
+++ b/misc/dashboard/codereview/dashboard/mail.go
@@ -9,6 +9,7 @@ import (
 	"time"
 
 	"appengine"
+	"appengine/datastore"
 )
 
 func init() {
@@ -35,6 +36,23 @@ func handleMail(w http.ResponseWriter, r *http.Request) {
 	}
 
 	c.Infof("Found issue %q", m[1])
+
+	// Track the MessageID.
+	key := datastore.NewKey(c, "CL", m[1], 0, nil)
+	err = datastore.RunInTransaction(c, func(c appengine.Context) error {
+		cl := new(CL)
+		err := datastore.Get(c, key, cl)
+		if err != nil && err != datastore.ErrNoSuchEntity {
+			return err
+		}
+		cl.LastMessageID = msg.Header.Get("Message-ID")
+		_, err = datastore.Put(c, key, cl)
+		return err
+	}, nil)
+	if err != nil {
+		c.Errorf("datastore transaction failed: %v", err)
+	}
+
 	// Update the CL after a delay to give Rietveld a chance to catch up.
 	UpdateCLLater(c, m[1], 10*time.Second)
 }

コアとなるコードの解説

misc/dashboard/codereview/dashboard/cl.go

  1. CL構造体へのLastMessageIDフィールドの追加:

    • LastMessageID string datastore:",noindex": CL構造体に新しいフィールドが追加されました。このフィールドは、そのコードレビュー(CL)に関連する最新のメールのMessage-IDを文字列として保存します。datastore:",noindex"`タグは、このフィールドがデータストアのインデックス作成の対象外であることを示し、単に値を保存する目的であることを明確にしています。
  2. handleAssign関数内のTODOコメント:

    • // TODO(dsymonds): Use cl.LastMessageID as the In-Reply-To header // when the appengine/mail package supports that.
    • このコメントは、将来的にappengine/mailパッケージがメールのIn-Reply-Toヘッダを設定する機能をサポートするようになった際に、保存されたLastMessageIDを使用してメールをスレッド化する計画があることを示しています。このコミット時点では、その機能が利用できないため、基盤の準備に留まっています。
  3. updateCL関数でのLastMessageIDの保持:

    • cl.LastMessageID = ocl.LastMessageID: CLエンティティをデータストアに保存する際に、既存のLastMessageIDが誤って上書きされないように、古いCLオブジェクト(ocl)からLastMessageIDをコピーして保持しています。これにより、一度記録されたMessage-IDが、その後のCLの更新プロセスで失われることなく永続的に保持されることが保証されます。

misc/dashboard/codereview/dashboard/mail.go

  1. appengine/datastoreパッケージのインポート:

    • "appengine/datastore": データストア操作を行うために必要なパッケージがインポートされました。
  2. handleMail関数でのMessage-IDの保存ロジック:

    • key := datastore.NewKey(c, "CL", m[1], 0, nil): 受信したメールがどのCLに関連するものかを特定するために、CLのキーを生成しています。m[1]はメールの件名などから抽出されたCLの識別子(issue番号など)と考えられます。
    • err = datastore.RunInTransaction(c, func(c appengine.Context) error { ... }, nil): データストアへの書き込み操作をトランザクション内で実行しています。これにより、複数の操作がアトミックに(すべて成功するか、すべて失敗するかのいずれかで)実行され、データの整合性が保たれます。
    • cl := new(CL): 新しいCL構造体のインスタンスを作成します。
    • err := datastore.Get(c, key, cl): 指定されたキーに対応するCLエンティティをデータストアから取得します。もしエンティティが存在しない場合(ErrNoSuchEntity)、それは新しいCLであると見なされ、エラーとはなりません。
    • cl.LastMessageID = msg.Header.Get("Message-ID"): 受信したメール(msg)のヘッダからMessage-IDを取得し、それをCLオブジェクトのLastMessageIDフィールドに設定します。
    • _, err = datastore.Put(c, key, cl): 更新されたCLオブジェクトをデータストアに保存します。

この一連の変更により、コードレビューダッシュボードは、各コードレビューに関連する最新のメールのMessage-IDを追跡し、保存できるようになりました。これは、将来的にメールのスレッド化機能を実装するための重要なステップとなります。

関連リンク

参考にした情報源リンク

  • https://github.com/golang/go/commit/1bdb788b2ea5147ff7847f7a401a9da994a5e360
  • Google検索: "Rietveld code review system"
  • Google検索: "Google App Engine mail API In-Reply-To"
  • Google検索: "email threading Message-ID In-Reply-To"
  • Go言語のappengine/datastoreに関する一般的な知識
  • メールヘッダ(Message-ID, In-Reply-To)に関する一般的な知識
  • トランザクション処理に関する一般的な知識# [インデックス 12996] ファイルの概要

このコミットは、Goプロジェクトのコードレビューダッシュボードシステムにおいて、コードレビューのスレッドメールのMessage-IDを記録するように変更を加えるものです。これにより、将来的に「R=...」形式の返信メールを適切にスレッド化(関連付ける)できるようになるための基盤を構築します。具体的には、CL構造体にLastMessageIDフィールドを追加し、受信したメールのMessage-IDをデータストアに保存するロジックが導入されています。

コミット

commit 1bdb788b2ea5147ff7847f7a401a9da994a5e360
Author: David Symonds <dsymonds@golang.org>
Date:   Mon Apr 30 22:47:51 2012 +1000

    misc/dashboard/codereview: record Message-ID of code review thread mails.
    
    This will allow us to properly thread "R=..." mails at a later time.
    
    R=golang-dev, r
    CC=golang-dev
    https://golang.org/cl/6135053

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/1bdb788b2ea5147ff7847f7a401a9da994a5e360

元コミット内容

misc/dashboard/codereview: record Message-ID of code review thread mails.

This will allow us to properly thread "R=..." mails at a later time.

R=golang-dev, r
CC=golang-dev
https://golang.org/cl/6135053

変更の背景

この変更の背景には、コードレビュープロセスにおけるメール通知の管理と、それらのメールを論理的な会話スレッドとして適切に扱う必要性があります。Goプロジェクトでは、Rietveld(Googleが開発したコードレビューツール)をベースとしたシステムが使用されており、コードレビューのコメントやステータス変更はメールで通知されます。

従来のシステムでは、これらのメールが必ずしも適切にスレッド化されていなかった可能性があります。特に、レビュー担当者からの「R=...」(Reviewed=...)といった形式の返信メールは、元のレビューコメントや変更リスト(CL: Change List)と関連付けられず、独立したメールとして扱われてしまうことが問題でした。

メールのスレッド化は、関連するメールをグループ化して表示することで、ユーザーが特定の話題やコードレビューの進捗を追いやすくするために非常に重要です。これを実現するためには、メールヘッダのMessage-IDIn-Reply-Toフィールドが鍵となります。Message-IDは各メールに一意に割り当てられる識別子であり、In-Reply-Toは返信元のメールのMessage-IDを指します。これにより、メールクライアントは関連するメールをスレッドとして表示できます。

このコミットは、将来的にIn-Reply-Toヘッダを適切に設定できるようにするための第一歩として、まずコードレビュー関連のメールのMessage-IDをシステム側で記録・保存することを目的としています。これにより、後続の変更でappengine/mailパッケージがIn-Reply-Toヘッダの設定をサポートするようになった際に、スムーズにスレッド化機能を実現できるようになります。

前提知識の解説

1. Rietveld (コードレビューシステム)

Rietveldは、Googleが開発したWebベースのコードレビューシステムです。PerforceやGitなどのバージョン管理システムと連携し、変更差分(diff)の表示、コメントの追加、レビューの承認(LGTM: Looks Good To Me)などの機能を提供します。Goプロジェクトのコードレビューシステムは、このRietveldをベースに構築されています。レビューの通知は通常、メールで行われます。

2. Google App Engine (GAE)

Google App Engineは、Googleが提供するPaaS(Platform as a Service)です。開発者はインフラの管理を気にすることなく、アプリケーションをデプロイ・実行できます。Goプロジェクトのコードレビューダッシュボードは、Google App Engine上で動作していると考えられます。GAEは、データストア(NoSQLデータベース)やメール送信サービスなど、様々なAPIを提供しています。

3. appengine/datastore

Google App Engineのデータストアは、スケーラブルなNoSQLデータベースサービスです。Go言語からはappengine/datastoreパッケージを通じてアクセスできます。データは「エンティティ」として保存され、各エンティティは「種類(Kind)」と一意の「キー」を持ちます。このコミットでは、CL(Change List)エンティティに新しいフィールドを追加し、その値を保存するためにデータストアが利用されています。

4. Message-IDIn-Reply-To (メールヘッダ)

  • Message-ID: 各メールに一意に割り当てられる識別子です。通常、<一意な文字列@ドメイン>のような形式をしています。メールクライアントやMTA(Mail Transfer Agent)によって自動的に生成されます。
  • In-Reply-To: このメールがどのメールへの返信であるかを示すヘッダです。返信元のメールのMessage-IDがここに記述されます。
  • References: In-Reply-Toと同様にスレッド化に利用されますが、こちらはスレッド内の過去のすべてのMessage-IDをリストします。

これらのヘッダを適切に利用することで、メールクライアントは関連するメールをグループ化し、会話の流れを追跡しやすくします。

5. 「R=...」メール

Goプロジェクトのコードレビューでは、レビュー担当者が変更を承認した際に「R=...」という形式のコメントをメールで送ることがあります。これは「Reviewed by ...」を意味し、レビューが完了したことを示します。このコミットの目的は、これらのメールが元のコードレビューのスレッドに正しく関連付けられるようにすることです。

技術的詳細

このコミットは、Goプロジェクトのコードレビューダッシュボードのバックエンドにおけるデータモデルとメール処理ロジックに焦点を当てています。

1. CL構造体へのLastMessageIDの追加

misc/dashboard/codereview/dashboard/cl.goファイルにおいて、CL構造体(Change List、つまりコードレビュー対象の変更セットを表すデータモデル)にLastMessageIDという新しいフィールドが追加されました。

type CL struct {
    // ... 既存のフィールド ...
    // Mail information.
    Subject       string   `datastore:",noindex"`
    Recipients    []string `datastore:",noindex"`
    LastMessageID string   `datastore:",noindex"` // <-- 追加
    // ... 既存のフィールド ...
}

このフィールドは、そのCLに関連する最新のメールのMessage-IDを保存するために使用されます。datastore:",noindex"タグは、このフィールドがデータストアのインデックス作成の対象外であることを示しており、クエリのパフォーマンスには影響せず、単に値を保存する目的であることを意味します。

2. LastMessageIDの保存ロジック

misc/dashboard/codereview/dashboard/mail.goファイルにおいて、受信したメールを処理するhandleMail関数内で、メールのMessage-IDを対応するCLエンティティのLastMessageIDフィールドに保存するロジックが追加されました。

func handleMail(w http.ResponseWriter, r *http.Request) {
    // ... 既存の処理 ...

    // Track the MessageID.
    key := datastore.NewKey(c, "CL", m[1], 0, nil)
    err = datastore.RunInTransaction(c, func(c appengine.Context) error {
        cl := new(CL)
        err := datastore.Get(c, key, cl)
        if err != nil && err != datastore.ErrNoSuchEntity {
            return err
        }
        cl.LastMessageID = msg.Header.Get("Message-ID") // <-- ここでMessage-IDを取得し設定
        _, err = datastore.Put(c, key, cl)
        return err
    }, nil)
    if err != nil {
        c.Errorf("datastore transaction failed: %v", err)
    }

    // ... 既存の処理 ...
}

この処理はトランザクション内で行われます。これは、データストアへの書き込みがアトミックに行われることを保証するためです。つまり、CLエンティティの取得、LastMessageIDの更新、そしてエンティティの保存が、すべて成功するか、すべて失敗するかのいずれかになります。これにより、データの整合性が保たれます。

msg.Header.Get("Message-ID")は、受信したメールのヘッダからMessage-IDフィールドの値を取得しています。

3. LastMessageIDの永続化

misc/dashboard/codereview/dashboard/cl.goupdateCL関数では、CLエンティティを更新する際に、既存のLastMessageIDが失われないようにする変更が加えられました。

func updateCL(c appengine.Context, n string) error {
    // ... 既存の処理 ...
    } else if err == nil {
        // LastMessageID and Reviewer need preserving.
        cl.LastMessageID = ocl.LastMessageID // <-- 既存のLastMessageIDを保持
        cl.Reviewer = ocl.Reviewer
    }
    // ... 既存の処理 ...
}

これは、CLエンティティがデータストアから読み込まれ、何らかの更新が行われる際に、LastMessageIDフィールドが誤って上書きされたり、空になったりするのを防ぐためのものです。これにより、一度保存されたLastMessageIDが、その後のCLの更新プロセスで永続的に保持されることが保証されます。

4. 将来の機能拡張への言及

misc/dashboard/codereview/dashboard/cl.gohandleAssign関数には、将来的な機能拡張に関するコメントが追加されています。

// TODO(dsymonds): Use cl.LastMessageID as the In-Reply-To header
// when the appengine/mail package supports that.

このコメントは、このコミットがMessage-IDを記録する基盤を構築するものであり、実際にメールのスレッド化を実現するためには、Google App Engineのメール送信API(appengine/mail)がIn-Reply-Toヘッダの設定をサポートする必要があることを示唆しています。このコミット時点ではその機能が利用できなかったため、将来の課題として残されています。

コアとなるコードの変更箇所

misc/dashboard/codereview/dashboard/cl.go

--- a/misc/dashboard/codereview/dashboard/cl.go
+++ b/misc/dashboard/codereview/dashboard/cl.go
@@ -45,8 +45,9 @@ type CL struct {
 	LGTMs       []string
 
 	// Mail information.
-	Subject    string   `datastore:",noindex"`
-	Recipients []string `datastore:",noindex"`
+	Subject       string   `datastore:",noindex"`
+	Recipients    []string `datastore:",noindex"`
+	LastMessageID string   `datastore:",noindex"`
 
 	// These are person IDs (e.g. "rsc"); they may be empty
 	Author   string
@@ -193,6 +194,8 @@ func handleAssign(w http.ResponseWriter, r *http.Request) {
 					Subject: cl.Subject + " (issue " + n + ")",
 					Body:    "R=" + rev + "\n\n(sent by gocodereview)",
 				}
+				// TODO(dsymonds): Use cl.LastMessageID as the In-Reply-To header
+				// when the appengine/mail package supports that.
 				sendMailLater.Call(c, msg)
 			}
 		}
@@ -339,7 +342,8 @@ func updateCL(c appengine.Context, n string) error {\
 		if err != nil && err != datastore.ErrNoSuchEntity {
 			return err
 		} else if err == nil {
-			// Reviewer is the only field that needs preserving.
+			// LastMessageID and Reviewer need preserving.
+			cl.LastMessageID = ocl.LastMessageID
 			cl.Reviewer = ocl.Reviewer
 		}
 		_, err = datastore.Put(c, key, cl)

misc/dashboard/codereview/dashboard/mail.go

--- a/misc/dashboard/codereview/dashboard/mail.go
+++ b/misc/dashboard/codereview/dashboard/mail.go
@@ -9,6 +9,7 @@ import (
 	"time"
 
 	"appengine"
+	"appengine/datastore"
 )
 
 func init() {
@@ -35,6 +36,23 @@ func handleMail(w http.ResponseWriter, r *http.Request) {
 	}
 
 	c.Infof("Found issue %q", m[1])
+
+	// Track the MessageID.
+	key := datastore.NewKey(c, "CL", m[1], 0, nil)
+	err = datastore.RunInTransaction(c, func(c appengine.Context) error {
+		cl := new(CL)
+		err := datastore.Get(c, key, cl)
+		if err != nil && err != datastore.ErrNoSuchEntity {
+			return err
+		}
+		cl.LastMessageID = msg.Header.Get("Message-ID")
+		_, err = datastore.Put(c, key, cl)
+		return err
+	}, nil)
+	if err != nil {
+		c.Errorf("datastore transaction failed: %v", err)
+	}
+
 	// Update the CL after a delay to give Rietveld a chance to catch up.
 	UpdateCLLater(c, m[1], 10*time.Second)
 }

コアとなるコードの解説

misc/dashboard/codereview/dashboard/cl.go

  1. CL構造体へのLastMessageIDフィールドの追加:

    • LastMessageID string datastore:",noindex": CL構造体に新しいフィールドが追加されました。このフィールドは、そのコードレビュー(CL)に関連する最新のメールのMessage-IDを文字列として保存します。datastore:",noindex"`タグは、このフィールドがデータストアのインデックス作成の対象外であることを示し、単に値を保存する目的であることを明確にしています。
  2. handleAssign関数内のTODOコメント:

    • // TODO(dsymonds): Use cl.LastMessageID as the In-Reply-To header // when the appengine/mail package supports that.
    • このコメントは、将来的にappengine/mailパッケージがメールのIn-Reply-Toヘッダを設定する機能をサポートするようになった際に、保存されたLastMessageIDを使用してメールをスレッド化する計画があることを示しています。このコミット時点では、その機能が利用できないため、基盤の準備に留まっています。
  3. updateCL関数でのLastMessageIDの保持:

    • cl.LastMessageID = ocl.LastMessageID: CLエンティティをデータストアに保存する際に、既存のLastMessageIDが誤って上書きされないように、古いCLオブジェクト(ocl)からLastMessageIDをコピーして保持しています。これにより、一度記録されたMessage-IDが、その後のCLの更新プロセスで失われることなく永続的に保持されることが保証されます。

misc/dashboard/codereview/dashboard/mail.go

  1. appengine/datastoreパッケージのインポート:

    • "appengine/datastore": データストア操作を行うために必要なパッケージがインポートされました。
  2. handleMail関数でのMessage-IDの保存ロジック:

    • key := datastore.NewKey(c, "CL", m[1], 0, nil): 受信したメールがどのCLに関連するものかを特定するために、CLのキーを生成しています。m[1]はメールの件名などから抽出されたCLの識別子(issue番号など)と考えられます。
    • err = datastore.RunInTransaction(c, func(c appengine.Context) error { ... }, nil): データストアへの書き込み操作をトランザクション内で実行しています。これにより、複数の操作がアトミックに(すべて成功するか、すべて失敗するかのいずれかで)実行され、データの整合性が保たれます。
    • cl := new(CL): 新しいCL構造体のインスタンスを作成します。
    • err := datastore.Get(c, key, cl): 指定されたキーに対応するCLエンティティをデータストアから取得します。もしエンティティが存在しない場合(ErrNoSuchEntity)、それは新しいCLであると見なされ、エラーとはなりません。
    • cl.LastMessageID = msg.Header.Get("Message-ID"): 受信したメール(msg)のヘッダからMessage-IDを取得し、それをCLオブジェクトのLastMessageIDフィールドに設定します。
    • _, err = datastore.Put(c, key, cl): 更新されたCLオブジェクトをデータストアに保存します。

この一連の変更により、コードレビューダッシュボードは、各コードレビューに関連する最新のメールのMessage-IDを追跡し、保存できるようになりました。これは、将来的にメールのスレッド化機能を実装するための重要なステップとなります。

関連リンク

参考にした情報源リンク

  • https://github.com/golang/go/commit/1bdb788b2ea5147ff7847f7a401a9da994a5e360
  • Google検索: "Rietveld code review system"
  • Google検索: "Google App Engine mail API In-Reply-To"
  • Google検索: "email threading Message-ID In-Reply-To"
  • Go言語のappengine/datastoreに関する一般的な知識
  • メールヘッダ(Message-ID, In-Reply-To)に関する一般的な知識
  • トランザクション処理に関する一般的な知識