diff --git a/models/issue.go b/models/issue.go
index 1727da1d50..05b17e4da5 100644
--- a/models/issue.go
+++ b/models/issue.go
@@ -739,7 +739,7 @@ type NewIssueOptions struct {
 	IsPull      bool
 }
 
-func newIssue(e *xorm.Session, opts NewIssueOptions) (err error) {
+func newIssue(e *xorm.Session, doer *User, opts NewIssueOptions) (err error) {
 	opts.Issue.Title = strings.TrimSpace(opts.Issue.Title)
 	opts.Issue.Index = opts.Repo.NextIssueIndex()
 
@@ -754,9 +754,6 @@ func newIssue(e *xorm.Session, opts NewIssueOptions) (err error) {
 		if milestone != nil {
 			opts.Issue.MilestoneID = milestone.ID
 			opts.Issue.Milestone = milestone
-			if err = changeMilestoneAssign(e, opts.Issue, -1); err != nil {
-				return err
-			}
 		}
 	}
 
@@ -785,6 +782,12 @@ func newIssue(e *xorm.Session, opts NewIssueOptions) (err error) {
 		return err
 	}
 
+	if opts.Issue.MilestoneID > 0 {
+		if err = changeMilestoneAssign(e, doer, opts.Issue, -1); err != nil {
+			return err
+		}
+	}
+
 	if opts.IsPull {
 		_, err = e.Exec("UPDATE `repository` SET num_pulls = num_pulls + 1 WHERE id = ?", opts.Issue.RepoID)
 	} else {
@@ -849,7 +852,7 @@ func NewIssue(repo *Repository, issue *Issue, labelIDs []int64, uuids []string)
 		return err
 	}
 
-	if err = newIssue(sess, NewIssueOptions{
+	if err = newIssue(sess, issue.Poster, NewIssueOptions{
 		Repo:        repo,
 		Issue:       issue,
 		LableIDs:    labelIDs,
@@ -1773,7 +1776,7 @@ func ChangeMilestoneIssueStats(issue *Issue) (err error) {
 	return sess.Commit()
 }
 
-func changeMilestoneAssign(e *xorm.Session, issue *Issue, oldMilestoneID int64) error {
+func changeMilestoneAssign(e *xorm.Session, doer *User, issue *Issue, oldMilestoneID int64) error {
 	if oldMilestoneID > 0 {
 		m, err := getMilestoneByRepoID(e, issue.RepoID, oldMilestoneID)
 		if err != nil {
@@ -1810,18 +1813,28 @@ func changeMilestoneAssign(e *xorm.Session, issue *Issue, oldMilestoneID int64)
 		}
 	}
 
+	if err := issue.loadRepo(e); err != nil {
+		return err
+	}
+
+	if oldMilestoneID > 0 || issue.MilestoneID > 0 {
+		if _, err := createMilestoneComment(e, doer, issue.Repo, issue, oldMilestoneID, issue.MilestoneID); err != nil {
+			return err
+		}
+	}
+
 	return updateIssue(e, issue)
 }
 
 // ChangeMilestoneAssign changes assignment of milestone for issue.
-func ChangeMilestoneAssign(issue *Issue, oldMilestoneID int64) (err error) {
+func ChangeMilestoneAssign(issue *Issue, doer *User, oldMilestoneID int64) (err error) {
 	sess := x.NewSession()
 	defer sess.Close()
 	if err = sess.Begin(); err != nil {
 		return err
 	}
 
-	if err = changeMilestoneAssign(sess, issue, oldMilestoneID); err != nil {
+	if err = changeMilestoneAssign(sess, doer, issue, oldMilestoneID); err != nil {
 		return err
 	}
 	return sess.Commit()
diff --git a/models/issue_comment.go b/models/issue_comment.go
index be7044a8e7..d128e2ebab 100644
--- a/models/issue_comment.go
+++ b/models/issue_comment.go
@@ -38,6 +38,8 @@ const (
 	CommentTypePullRef
 	// Labels changed
 	CommentTypeLabel
+	// Milestone changed
+	CommentTypeMilestone
 )
 
 // CommentTag defines comment tag type
@@ -58,9 +60,13 @@ type Comment struct {
 	PosterID        int64 `xorm:"INDEX"`
 	Poster          *User `xorm:"-"`
 	IssueID         int64 `xorm:"INDEX"`
-	CommitID        int64
 	LabelID         int64
 	Label           *Label `xorm:"-"`
+	OldMilestoneID  int64
+	MilestoneID     int64
+	OldMilestone    *Milestone `xorm:"-"`
+	Milestone       *Milestone `xorm:"-"`
+	CommitID        int64
 	Line            int64
 	Content         string `xorm:"TEXT"`
 	RenderedContent string `xorm:"-"`
@@ -204,6 +210,36 @@ func (c *Comment) LoadLabel() error {
 	return nil
 }
 
+// LoadMilestone if comment.Type is CommentTypeMilestone, then load milestone
+func (c *Comment) LoadMilestone() error {
+	if c.OldMilestoneID > 0 {
+		var oldMilestone Milestone
+		has, err := x.ID(c.OldMilestoneID).Get(&oldMilestone)
+		if err != nil {
+			return err
+		} else if !has {
+			return ErrMilestoneNotExist{
+				ID: c.OldMilestoneID,
+			}
+		}
+		c.OldMilestone = &oldMilestone
+	}
+
+	if c.MilestoneID > 0 {
+		var milestone Milestone
+		has, err := x.ID(c.MilestoneID).Get(&milestone)
+		if err != nil {
+			return err
+		} else if !has {
+			return ErrMilestoneNotExist{
+				ID: c.MilestoneID,
+			}
+		}
+		c.Milestone = &milestone
+	}
+	return nil
+}
+
 // MailParticipants sends new comment emails to repository watchers
 // and mentioned people.
 func (c *Comment) MailParticipants(e Engine, opType ActionType, issue *Issue) (err error) {
@@ -233,15 +269,17 @@ func createComment(e *xorm.Session, opts *CreateCommentOptions) (_ *Comment, err
 		LabelID = opts.Label.ID
 	}
 	comment := &Comment{
-		Type:      opts.Type,
-		PosterID:  opts.Doer.ID,
-		Poster:    opts.Doer,
-		IssueID:   opts.Issue.ID,
-		LabelID:   LabelID,
-		CommitID:  opts.CommitID,
-		CommitSHA: opts.CommitSHA,
-		Line:      opts.LineNum,
-		Content:   opts.Content,
+		Type:           opts.Type,
+		PosterID:       opts.Doer.ID,
+		Poster:         opts.Doer,
+		IssueID:        opts.Issue.ID,
+		LabelID:        LabelID,
+		OldMilestoneID: opts.OldMilestoneID,
+		MilestoneID:    opts.MilestoneID,
+		CommitID:       opts.CommitID,
+		CommitSHA:      opts.CommitSHA,
+		Line:           opts.LineNum,
+		Content:        opts.Content,
 	}
 	if _, err = e.Insert(comment); err != nil {
 		return nil, err
@@ -367,6 +405,17 @@ func createLabelComment(e *xorm.Session, doer *User, repo *Repository, issue *Is
 	})
 }
 
+func createMilestoneComment(e *xorm.Session, doer *User, repo *Repository, issue *Issue, oldMilestoneID, milestoneID int64) (*Comment, error) {
+	return createComment(e, &CreateCommentOptions{
+		Type:           CommentTypeMilestone,
+		Doer:           doer,
+		Repo:           repo,
+		Issue:          issue,
+		OldMilestoneID: oldMilestoneID,
+		MilestoneID:    milestoneID,
+	})
+}
+
 // CreateCommentOptions defines options for creating comment
 type CreateCommentOptions struct {
 	Type  CommentType
@@ -375,11 +424,13 @@ type CreateCommentOptions struct {
 	Issue *Issue
 	Label *Label
 
-	CommitID    int64
-	CommitSHA   string
-	LineNum     int64
-	Content     string
-	Attachments []string // UUIDs of attachments
+	OldMilestoneID int64
+	MilestoneID    int64
+	CommitID       int64
+	CommitSHA      string
+	LineNum        int64
+	Content        string
+	Attachments    []string // UUIDs of attachments
 }
 
 // CreateComment creates comment of issue or commit.
diff --git a/models/pull.go b/models/pull.go
index 382738bf29..e680480978 100644
--- a/models/pull.go
+++ b/models/pull.go
@@ -470,7 +470,7 @@ func NewPullRequest(repo *Repository, pull *Issue, labelIDs []int64, uuids []str
 		return err
 	}
 
-	if err = newIssue(sess, NewIssueOptions{
+	if err = newIssue(sess, pull.Poster, NewIssueOptions{
 		Repo:        repo,
 		Issue:       pull,
 		LableIDs:    labelIDs,
diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index 546b9489b1..cd2b67f8da 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -543,6 +543,9 @@ issues.label_templates.use = Use this label set
 issues.label_templates.fail_to_load_file = Failed to load label template file '%s': %v
 issues.add_label_at = `added the <div class="ui label" style="color: %s; background-color: %s">%s</div> label %s`
 issues.remove_label_at = `removed the <div class="ui label" style="color: %s; background-color: %s">%s</div> label %s`
+issues.add_milestone_at = `added this to the <b>%s</b> milestone %s`
+issues.change_milestone_at = `modified the milestone from <b>%s</b> to <b>%s</b> %s`
+issues.remove_milestone_at = `removed this from the <b>%s</b> milestone %s`
 issues.open_tab = %d Open
 issues.close_tab = %d Closed
 issues.filter_label = Label
diff --git a/options/locale/locale_zh-CN.ini b/options/locale/locale_zh-CN.ini
index 840ff36b5f..dfb9bbb172 100644
--- a/options/locale/locale_zh-CN.ini
+++ b/options/locale/locale_zh-CN.ini
@@ -503,6 +503,9 @@ issues.label_templates.use=加载标签模板
 issues.label_templates.fail_to_load_file=加载标签模板文件 '%s' 时发生错误:%v
 issues.add_label_at = ` %[4]s 添加了标签 <div class="ui label" style="color: %[1]s; background-color: %[2]s">%[3]s</div>`
 issues.remove_label_at = ` %[4]s 删除了标签 <div class="ui label" style="color: %[1]s; background-color: %[2]s">%[3]s</div>`
+issues.add_milestone_at = ` %[2]s 添加了里程碑 <b>%[1]s</b>`
+issues.change_milestone_at = `%[3]s 修改了里程碑从 <b>%[1]s</b> 到 <b>%[2]s</b>`
+issues.remove_milestone_at = `%[2]s 删除了里程碑 <b>%[1]s</b>`
 issues.open_tab=%d 个开启中
 issues.close_tab=%d 个已关闭
 issues.filter_label=标签筛选
diff --git a/public/js/index.js b/public/js/index.js
index e52da6e5ba..086ec7656f 100644
--- a/public/js/index.js
+++ b/public/js/index.js
@@ -168,6 +168,12 @@ function initCommentForm() {
         var $list = $('.ui' + select_id + '.list');
         var hasUpdateAction = $menu.data('action') == 'update';
 
+        $(select_id).dropdown('setting', 'onHide', function(){
+            if (hasUpdateAction) {
+                location.reload();
+            }
+        });
+
         $menu.find('.item:not(.no-select)').click(function () {
             $(this).parent().find('.item').each(function () {
                 $(this).removeClass('selected active')
diff --git a/routers/api/v1/repo/issue.go b/routers/api/v1/repo/issue.go
index ac0289c412..c65f4b7063 100644
--- a/routers/api/v1/repo/issue.go
+++ b/routers/api/v1/repo/issue.go
@@ -168,7 +168,7 @@ func EditIssue(ctx *context.APIContext, form api.EditIssueOption) {
 		issue.MilestoneID != *form.Milestone {
 		oldMilestoneID := issue.MilestoneID
 		issue.MilestoneID = *form.Milestone
-		if err = models.ChangeMilestoneAssign(issue, oldMilestoneID); err != nil {
+		if err = models.ChangeMilestoneAssign(issue, ctx.User, oldMilestoneID); err != nil {
 			ctx.Error(500, "ChangeMilestoneAssign", err)
 			return
 		}
diff --git a/routers/api/v1/repo/pull.go b/routers/api/v1/repo/pull.go
index 4c10024bfb..9a38d37528 100644
--- a/routers/api/v1/repo/pull.go
+++ b/routers/api/v1/repo/pull.go
@@ -241,7 +241,7 @@ func EditPullRequest(ctx *context.APIContext, form api.EditPullRequestOption) {
 		issue.MilestoneID != form.Milestone {
 		oldMilestoneID := issue.MilestoneID
 		issue.MilestoneID = form.Milestone
-		if err = models.ChangeMilestoneAssign(issue, oldMilestoneID); err != nil {
+		if err = models.ChangeMilestoneAssign(issue, ctx.User, oldMilestoneID); err != nil {
 			ctx.Error(500, "ChangeMilestoneAssign", err)
 			return
 		}
diff --git a/routers/repo/issue.go b/routers/repo/issue.go
index b2490e242a..76440359ef 100644
--- a/routers/repo/issue.go
+++ b/routers/repo/issue.go
@@ -384,22 +384,27 @@ func ValidateRepoMetas(ctx *context.Context, form auth.CreateIssueForm) ([]int64
 		return nil, 0, 0
 	}
 
-	// Check labels.
-	labelIDs, err := base.StringsToInt64s(strings.Split(form.LabelIDs, ","))
-	if err != nil {
-		return nil, 0, 0
-	}
-	labelIDMark := base.Int64sToMap(labelIDs)
+	var labelIDs []int64
 	hasSelected := false
-	for i := range labels {
-		if labelIDMark[labels[i].ID] {
-			labels[i].IsChecked = true
-			hasSelected = true
+	// Check labels.
+	if len(form.LabelIDs) > 0 {
+		labelIDs, err = base.StringsToInt64s(strings.Split(form.LabelIDs, ","))
+		if err != nil {
+			return nil, 0, 0
+		}
+		labelIDMark := base.Int64sToMap(labelIDs)
+
+		for i := range labels {
+			if labelIDMark[labels[i].ID] {
+				labels[i].IsChecked = true
+				hasSelected = true
+			}
 		}
 	}
+
+	ctx.Data["Labels"] = labels
 	ctx.Data["HasSelectedLabel"] = hasSelected
 	ctx.Data["label_ids"] = form.LabelIDs
-	ctx.Data["Labels"] = labels
 
 	// Check milestone.
 	milestoneID := form.MilestoneID
@@ -617,6 +622,11 @@ func ViewIssue(ctx *context.Context) {
 				ctx.Handle(500, "LoadLabel", err)
 				return
 			}
+		} else if comment.Type == models.CommentTypeMilestone {
+			if err = comment.LoadMilestone(); err != nil {
+				ctx.Handle(500, "LoadMilestone", err)
+				return
+			}
 		}
 	}
 
@@ -625,7 +635,6 @@ func ViewIssue(ctx *context.Context) {
 		canDelete := false
 
 		if ctx.IsSigned && pull.HeadBranch != "master" {
-
 			if err := pull.GetHeadRepo(); err != nil {
 				log.Error(4, "GetHeadRepo: %v", err)
 			} else if ctx.User.IsWriterOfRepo(pull.HeadRepo) {
@@ -729,7 +738,7 @@ func UpdateIssueMilestone(ctx *context.Context) {
 
 	// Not check for invalid milestone id and give responsibility to owners.
 	issue.MilestoneID = milestoneID
-	if err := models.ChangeMilestoneAssign(issue, oldMilestoneID); err != nil {
+	if err := models.ChangeMilestoneAssign(issue, ctx.User, oldMilestoneID); err != nil {
 		ctx.Handle(500, "ChangeMilestoneAssign", err)
 		return
 	}
diff --git a/templates/repo/issue/view_content.tmpl b/templates/repo/issue/view_content.tmpl
index ef32d0ba1e..39e0587d94 100644
--- a/templates/repo/issue/view_content.tmpl
+++ b/templates/repo/issue/view_content.tmpl
@@ -151,7 +151,16 @@
 							<img src="{{.Poster.RelAvatarLink}}">
 						</a>
 						<span class="text grey"><a href="{{.Poster.HomeLink}}">{{.Poster.Name}}</a>
-						{{if .Content}}{{$.i18n.Tr "repo.issues.add_label_at" .Label.ForegroundColor .Label.Color  .Label.Name $createdStr | Safe}}{{else}}{{$.i18n.Tr "repo.issues.remove_label_at" .Label.ForegroundColor .Label.Color .Label.Name $createdStr | Safe}}{{end}}</span>
+						{{if .Content}}{{$.i18n.Tr "repo.issues.add_label_at" .Label.ForegroundColor .Label.Color .Label.Name $createdStr | Safe}}{{else}}{{$.i18n.Tr "repo.issues.remove_label_at" .Label.ForegroundColor .Label.Color .Label.Name $createdStr | Safe}}{{end}}</span>
+					</div>
+				{{else if eq .Type 8}}
+					<div class="event">
+						<span class="octicon octicon-primitive-dot"></span>
+						<a class="ui avatar image" href="{{.Poster.HomeLink}}">
+							<img src="{{.Poster.RelAvatarLink}}">
+						</a>
+						<span class="text grey"><a href="{{.Poster.HomeLink}}">{{.Poster.Name}}</a>
+						{{if gt .OldMilestoneID 0}}{{if gt .MilestoneID 0}}{{$.i18n.Tr "repo.issues.change_milestone_at" .OldMilestone.Name .Milestone.Name $createdStr | Safe}}{{else}}{{$.i18n.Tr "repo.issues.remove_milestone_at" .OldMilestone.Name $createdStr | Safe}}{{end}}{{else if gt .MilestoneID 0}}{{$.i18n.Tr "repo.issues.add_milestone_at" .Milestone.Name $createdStr | Safe}}{{end}}</span>
 					</div>
 				{{end}}