Chiroru's Diary

日々の学びをちょこちょこメモしていきます

git rebaseのちょっとしたまとめ

rebase、分かったつもりになってまたわからないを繰り返しているので少しまとめておきます。

rebaseとは

  • 現在いるブランチを、rebaseしてきたブランチの先頭にくっつけます。
  • この時、現在いるブランチのコミットを一瞬退避(一時保存)し、rebaseしてきたブランチに現在いるブランチをgit reset --hardして、保存していたコミットを上にのせる仕組みです。
  • リベースを行うとコミットのidが変わり、別物として扱われます。これはコミットが新しく作られるためであり、リモートリポジトリに既にpushしていた場合は、ローカルと一致しなくなってしまいpushできなくなります。
  • 上記のパターンでpushがrejectされた場合、git push -fで対応します。
  • rebaseするとその際のコミットが作られません。(mergeするとマージコミットができちゃいます。)

git rebase

rebaseはこんな感じで使えます。これはgit checkout branchB+ git rebase branchAを組み合わせたものです。

# branchAをbranchBにリベース(Bが先行する)
$ git rebase branchA branch B

git pullとpull --rebaseについて

git pullは以下のように、fetch + mergeです。

# リモートBからリモート追跡Bにコピー
$ git fetch

# リモート追跡B(=リモートBのコピー)からローカルBにマージ
$ git merge origin/master

# 上記二つをいっぺんに
$ git pull origin master

一方git pull --rebase(←オプション)は、fetch + rebaseをします。

$ git pull --rebase origin master

git rebase -i

git rebase --interactive。過去のコミットを編集することができます。
git rebase -i <編集したいコミットID>~と使用します。

ちなみに間違えて編集した場合は、git reset --hard <編集前のコミットID>で修正できました。

間違えたパターンの再現

3つのcommitを用意しました。 (1・2はpush済みで3が未push状態でした。)

$ git log --oneline
058deaf (HEAD -> practise) commit3
b100354 (origin/practise) commit2
4ceebb5 commit1

push済みでもrebase -iで編集はできます。しかしこの時間違えてcommit2を、commit1にsquashしてしまいました。

pick 4ceebb5 commit1
s b100354 commit2
pick 058deaf commit3

# Rebase bf5a488..058deaf onto bf5a488 (3 commands)
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup <commit> = like "squash", but discard this commit's log message
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label

その結果こうなります。

$ git log --oneline
75df8ce (HEAD -> practise) commit3
7d51144 commit1

$ git reflog
75df8ce (HEAD -> practise) HEAD@{0}: rebase (finish): returning to refs/heads/practise
75df8ce (HEAD -> practise) HEAD@{1}: rebase (pick): commit3
7d51144 HEAD@{2}: rebase (squash): commit1
4ceebb5 HEAD@{3}: rebase (start): checkout 4ceebb5~
058deaf HEAD@{4}: commit: commit3
b100354 (origin/practise) HEAD@{5}: commit: commit2
4ceebb5 HEAD@{6}: commit: commit1

間違えた編集をした場合、git reset --hard <編集前のコミットID>で対応できました。今回の場合は058deaf時点に戻しました。

reset --hardを使用することでコミットが抹消しないか心配だったのですが、問題なく意図通りrebase編集前状態に戻すことができました!

$ git reset --hard 058deaf
HEAD is now at 058deaf commit3

$ git reflog
058deaf (HEAD -> practise) HEAD@{0}: reset: moving to 058deaf

$ git log --oneline
058deaf (HEAD -> practise) commit3
b100354 (origin/practise) commit2
4ceebb5 commit1

そして本来やりたかった、commit3をcommit2にsquashしました。
※この場合であれば、$ git commit --amendでもOKですが今回は敢えてrebase -i使っています

$ git rebase -i 4ceebb5~
pick 4ceebb5 commit1
pick b100354 commit2
s 058deaf commit3

大丈夫そうですね。

$ git log --oneline
2a18a66 (HEAD -> practise) commit2
4ceebb5 commit1

$ git reflog
2a18a66 (HEAD -> practise) HEAD@{0}: rebase (finish): returning to refs/heads/practise
2a18a66 (HEAD -> practise) HEAD@{1}: rebase (squash): commit2
b100354 (origin/practise) HEAD@{2}: rebase (start): checkout 4ceebb5~
058deaf HEAD@{3}: reset: moving to 058deaf
75df8ce HEAD@{4}: rebase (finish): returning to refs/heads/practise
75df8ce HEAD@{5}: rebase (pick): commit3
7d51144 HEAD@{6}: rebase (squash): commit1
4ceebb5 HEAD@{7}: rebase (start): checkout 4ceebb5~

rebaseによってcommitIDが変更されてしまっているので、このままでは普通にpushできません。

なので以下のように強制pushします。--force-with-leaseを利用すれば、他の人のコミットは維持したまま上書きすることができます。

$ git push origin ブランチ --force-with-lease 

Fjordのメンターodaillyさんのまとめがわかりやすかったです🙆‍♀️

Scrapboxに草を生やす

最近自分のメモ書き用にScrapboxを使用しており、結構使い心地が良くなってきました。ブログと違い、気軽にまとめたり小さな単位でアウトプットできることが良いなと思っています。

利用頻度がある程度増えてきたので、Scrapboxに草を生やすことにしました。

Pixela

Githubのグラフのような物を提供してくれるWebAPIです。 今回はScrapboxの投稿を可視化するのに利用しました。

手順

Pixelaにユーザー登録

$ curl -X POST https://pixe.la/v1/users -d '{"token":"XXXXX", "username":"chiroru", "agreeTermsOfService":"yes", "notMinor":"yes"}'

=>{"message":"Success. Let's visit ~~~ , it is your profile page!","isSuccess":true}

今回利用するグラフの作成

$ curl -X POST https://pixe.la/v1/users/chiroru/graphs -H 'X-USER-TOKEN:XXXXX' -d '{"id":"scrapbox-graph", "name":"my-scrapbox-graph", "unit":"updates", "type":"int", "color":"shibafu", "timezone":"Asia/Tokyo"}'

=>{"message":"Success.","isSuccess":true}

Webhook作成

Webhookとは、あるアプリケーションの更新情報を、他のアプリに提供する仕組みのことで、今回でいうとScrapboxの投稿情報を、Pixelaに提供してグラフの草を生やすのに利用します。

$ curl -X POST https://pixe.la/v1/users/chiroru/webhooks -H 'X-USER-TOKEN:XXXXX' -d '{"graphID":"scrapbox-graph", "type":"increment"}'

=>{"message":"Success.","isSuccess":true,"webhookHash":"~~~~~"}

※ 参考:Webhookとは?

ScrapboxでNotificationsに設定する

Project settings/Notificationsに、以下を追加する。

https://pixe.la/v1/users/chiroru/webhooks/webhookHash

Scrapboxで表示する

以下のようにURLを追加します。

https://pixe.la/v1/users/chiroru/graphs/scrapbox-graph.svg

するとこんな感じで表示できます↓あっという間!

f:id:chiroru_memo:20201229234653p:plain

参考

2020 Rails Girlsの活動を振返る

この記事はRails Girls Japan Advent Calendar 2020の15日目の記事です。
前回はcobachieさんのRails Girls Gathering Japan を開催した理由でした。

今回は、 私のRails Girlsでの1年の活動を振返りたいと思います。

Rails Girls Nagoyaでオーガナイザーをした

1月31日・2月1日にRails Girls Nagoya 5thが開催され、オーガナイザーとして参加しました。この頃は、新型コロナウイルスが流行り始めた時期で開催が懸念されましたが、どうにかギリギリ予定通り実施できました。

私がオーガナイザーになろうと思った理由は、初めて参加したコミュニティのRails Girlsでの体験が、とても楽しくて忘れられない経験となり、次は「自分がいただいたものを、また他の誰かに届けたい」と思ったためです。

運営として私が大事にしたのは「自分の目標、また参加者の方の目標(目的)はそれぞれ異なる」ということです。 だからその上で私は、「個人の目標を達成してもらいかつ楽しんでもらう」ことを目標に、それをオープニングの言葉としました。

当日はGirls10名、運営12名での比較的小規模(?)な開催でありましたが、いちオーガナイザーとしてまたRails Girlsに携わることができ、とてもとても幸せでした🌸

Nagoya More! / Nagano More!がある

Rails Girlsのイベントが終わっても、月1ペースのMore!勉強会に参加できます💪

どちらもオンラインでの勉強会で、Nagoya More!では、『現場で使える Ruby on Rails 5速習実践ガイド』輪読会・『Railsの教科書』各自勉強・もくもく会watchパーティー(ジョブスの映画鑑賞)などに今年は取り組みました。

またNagoyaと親和性の高いNagano More!にも参加しており、Naganoでは『webを支える技術』の輪読会と、ビブリオトークを今年やってきました。(ビブリオトークは、現在自分が読んでいる本をジャンル問わず紹介する企画です。 )
今年は本を紹介されてばかりだったので、来年はもっとたくさん本を読んで紹介する側になっていきたいなぁと思います📚

Tokyo More!にもオンライン参加

コロナの影響で、今まで参加できなかったTokyo More!(※コロナ以前は、オフライン開催)にも参加させていただきました🤲

igaigaさんが座学をしてくださる会で、参加者もたくさんいてワイワイしていました!
(その時のブログ→RailsGirls Online More!に参加しました!)

Rails Girls Gathering Japanの運営スタッフに挑戦

先日12月12日に、Rails Girls Japan初のオンラインLT会を実施しました🎉
今年の締めくくりに、私は有志で募られたスタッフとして、運営に携わらせていただきました。

およそ2ヶ月くらい前から打ち合わせが始まり、イベントの名前を決めたり、募集要項の作成や告知、また当日の運営等全部が初めてでしたが、運営チームで力を合わせ、盛況のうちに終えることができました🙏

予想以上に参加申し込みをしてくださる方がおり(イベント中にも申し込みしてくださる方がたくさんおりました!)、当日は影武者ながらとても緊張していましたが、SNSで拡散していただいたコメントを読み、勇気を出して運営やって良かったなぁと嬉しい気持ちになりました。

まとめ

Rails Girlsというコミュニティを通じて、たくさんの素敵な人々と出会うことができました。 そして (私が今通っている)FjordBootCampという素晴らしいコミュニティに出会えたのも、Rails Girlsで出会った方々のお陰です🌸
これからもこの場所を、この輪を大切にし、そして今度はまた別の誰かに運ぶことができるよう、過ごしていければなぁと思います。

Prettierの導入

【目次】

インストール

// prettierをインストール
$ npm i -D prettier

ESLintと一緒に使うために

  • prettier-eslint
  • prettier-eslint-cli

ESLintの記述ルール(JavaScript Standard Style)を反映するために

  • eslint-config-standard

をインストールします。

$ npm i -D prettier-eslint prettier-eslint-cli eslint-config-standard

package.jsonにPretterのコマンドを追加します。 --writeオプションを指定することで整形後の内容で保存できます。コードフォーマッターを適用する対象をformatに記述します。

// package.json

{
  "scripts": {
    "format": "prettier --write '/**/*.js'"
  },
  "devDependencies": {
    "prettier": "^2.1.1"
  }
}

実行

$ npm run formatで実行できます。 ここではeslint自動修正と合わせて実行しています。

$ npx eslint test2.js

/Users/name/xxx/Javascript/test2.js
  1:4   error  Multiple spaces found before 'hoge'                    no-multi-spaces
  1:7   error  'hoge' is assigned a value but never used              no-unused-vars
  1:7   error  'hoge' is never reassigned. Use 'const' instead        prefer-const
  1:11  error  Multiple spaces found before '='                       no-multi-spaces
  1:15  error  'func' is not defined                                  no-undef
  1:19  error  Unexpected whitespace between function name and paren  func-call-spacing
  2:1   error  Expected indentation of 2 spaces but found 4           indent
  2:5   error  'parameter1' is not defined                            no-undef
  2:16  error  Multiple spaces found before 'parameter2'              no-multi-spaces
  2:19  error  'parameter2' is not defined                            no-undef
  2:31  error  'parameter3' is not defined                            no-undef
  2:42  error  Multiple spaces found before 'parameter4'              no-multi-spaces
  2:46  error  'parameter4' is not defined                            no-undef
  3:1   error  Expected indentation of 2 spaces but found 3           indent
  3:4   error  'parameter6' is not defined                            no-undef
  3:15  error  Multiple spaces found before 'parameter7'              no-multi-spaces
  3:17  error  'parameter7' is not defined                            no-undef
  4:1   error  Expected indentation of 2 spaces but found 9           indent
  4:10  error  'parameter8' is not defined                            no-undef
  4:20  error  Unexpected trailing comma                              comma-dangle
  5:1   error  Expected indentation of 0 spaces but found 14          indent
  5:16  error  Extra semicolon                                        semi

✖ 22 problems (22 errors, 0 warnings)
  13 errors and 0 warnings potentially fixable with the `--fix` option.

$ npx eslint test2.js --fix

/Users/name/xxx/Javascript/test2.js
  1:7   error  'hoge' is assigned a value but never used  no-unused-vars
  1:14  error  'func' is not defined                      no-undef
  2:3   error  'parameter1' is not defined                no-undef
  2:15  error  'parameter2' is not defined                no-undef
  2:27  error  'parameter3' is not defined                no-undef
  2:39  error  'parameter4' is not defined                no-undef
  3:3   error  'parameter6' is not defined                no-undef
  3:15  error  'parameter7' is not defined                no-undef
  4:3   error  'parameter8' is not defined                no-undef

✖ 9 problems (9 errors, 0 warnings)

$ npm run format

> javascript@1.0.0 format /Users/name/xxx/Javascript
> prettier-eslint --write '/Users/name/xxx/Javascript/*.js'

success formatting 1 file with prettier-eslint
1 file was unchanged

参考

ページごとにサイドバーのコンテンツを切替える

お手伝いしているRailsのサービスサイトで、サイドバーに全ページに共通の広告を表示しているのですが、掲載数の変更に伴いページごとに種類を変えて広告バナーを表示することにしました。

現状としてはapplication.html.erbで統一されたサイドバーを表示していますが、 これをページごとに分割するのに「名前付きyield」が使えそうです。

【目次】

名前付きyieldの指定

まずapplication.html.slim側では、= yield :nameの形式でyieldに名前をつけます。

現在広告バナーはページのcol-md-3の部分に表示しているため、col-md-9はそのまま現状のものを表示できるよう= yieldを、そしてxxxページ・yyyページでそれぞれバナーを変更したいcol-md-3の内部に名前付きyieldを記述します。

.row
  .col-md-9
    = yield
  .col-md-3
    .sidebar-content
      = yield :xxx
      = yield :yyy

viewのメイン部分は常に「名前なしyield」としてレンダリングされるため、コンテンツを名前付きのyieldとしてレンダリングするには、content_forメソッドを使用します。

content_forメソッドでレイアウトに挿入

名前付きのyieldとしてレイアウトに挿入する場合、各ページ(index.html.slim)でcontent_for :name doの形式で指定します。

xxx/index.html.slim

= h1 名前なしyieldとして挿入される

- content_for :xxx do
  .sidebar-content
    h2 名前付きyieldとして挿入される
    .card

yyy/index.html.slim

= h1 名前なしyieldとして挿入される(yyy)

- content_for :yyy do
  .sidebar-content
    h2 名前付きyieldとして挿入される(yyy)
    .card

これらのレンダリング結果がレイアウトに挿入されると、最終的には以下のようなHTMLとして出力されます。

xxx/index.html

<div class="row">
  <div class="col-md-9">
    <h1>名前なしyieldとして挿入される</h1>
  <div class="col-md-3">
    <div class="sidebar-content">
      <h2>名前付きyieldとして挿入される</h2>
      <div class="card">

yyy/index.html

<div class="row">
  <div class="col-md-9">
    <h1>名前なしyieldとして挿入される(yyy)</h1>
  <div class="col-md-3">
    <div class="sidebar-content">
      <h2>名前付きyieldとして挿入される(yyy)</h2>
      <div class="card">

これでxxxページ・yyyページそれぞれ異なる広告バナーを表示させることができるようになりました!

レイアウトをコントローラーごとに指定するのもいいかも

今回は各ページで広告バナーのみ切替がしたかったので使用しませんでしたが、コントローラーにレイアウトを指定することで表示を変える方法も良さそうです。

例えばxxx_controllerのshowページでは、applicationファイルのようなサイドバナー広告の表示が不要であるとします。

そんな時はviews/layouts下に、別途レイアウトファイルを作成し、コントローラーでlayoutメソッドを指定することで切り替えられます。
今回はバナーなしのno_banner.html.slimを作成したとします。

作成できたら、xxxコントローラーにて以下のように指定しましょう。

layout "no_banner", only: :show

# または

def show
  render layout: "no_banner"
end

参考

Githubのデフォルトブランチがmainになった

【目次】

mainになっていた

Githubのデフォルトのブランチが「master」から「main」になったようです。

GitHub、これから作成するリポジトリのデフォルトブランチ名が「main」に。「master」から「main」へ変更

これを知らずにリポジトリを作成後、masterにプッシュしてしまいInitial commitがうまくいきませんでした。

f:id:chiroru_memo:20201012220834p:plain
github上で別物として扱われてしまっております

まずこの解決方法として、以下のようにmasterブランチのコミットをmainブランチへ移しました。

$ git fetch origin
* [new branch]      main       -> origin/main

$ git checkout main
Switched to a new branch 'main'

$ git cherry-pick コミットID

$ git push origin main

※コミットIDは、Github上でbranchのcommitsから確認できます

f:id:chiroru_memo:20201012225119p:plain
コミットIDをコピー

本来Github上でプルリクを出すことができるはずなのですが、masterブランチからmainブランチへはなぜかプルリクが出せない状態になっていた(何も表示がなかった)ので、今回はターミナル上で対応しました。

そもそもgit init時にmasterブランチが作成されている

git initで初期化する際に、masterブランチが生成されてしまっています。
デフォルトのブランチ名がmasterからmainに変わったのはGithubのみであるので、これらを一致させると良いでしょう。

このgit initで生成されるブランチ名をmainにするには、init.defaultBranchで設定できるようです。

※これはgit 2.28以上のバージョンでのみになります。

Git でデフォルトのブランチ名を master 以外に変更する方法

$ git config --global init.defaultBranch main

リポジトリをcloneしてきてから作業するのも良いかも

最初にGithubリポジトリを作成してきて、$ git clone リポジトリのURLでクローンしてこれば、すでにあるmainブランチ上で作業を開始することもできます。

リポジトリをクローンする

$ git clone URL

$ cd リポジトリ

$ git branch
* main

$ git add
・
・
$ git push origin main

zshならgitの補完機能も使えるかも

(私はbashなのでこちらは試せていないのですが)補完機能を利用して、持っているブランチを確認しつつ作業することもできるようです。

zsh で git コマンドの補完を有効にする

[追記] ブランチ名をあとで変更すれば良いだけかも

こちらを書いた時頭になかったのですが、普通にブランチ名をpush前にmainに変更するのでも良いですね。

$ git branch -m <変更するブランチ名> <変更後のブランチ名>

// 現在使用しているブランチを変更する
$ git branch -m <変更後のブランチ名>

Machida.rb #05に参加しました!

毎月第1金曜日に開催されているMachida.rbに、前回に引き続き2回目の参加をしました!
【目次】

今回はRubyで簡単な問題を解いてレビューし合う勉強会でした💪その中の問題を抜粋して書きます。

Rubyの問題を解いてみた

FizzBuzz

有名なFizzBuzz問題の省略版です。1~20の数字を出力するうち、3の倍数ではFizz・5の倍数ではBuzz・3と5の倍数ではFizzBuzzと出力させます。

最初のコードはこちら。

(1..20).each {|num|
  if num % 15 == 0
    puts "fizzbuzz"
  elsif num % 3 == 0
    puts "fizz"
  elsif num % 5 == 0
    puts "buzz"
  else
    puts num
  end
}

このコードで注目したのは「putsを毎回書いている」という点です。

これは以下のようにif文を()で括りそれに対してputsすることで、何度も書いていた部分が改善できるようになりました。

(1..20).each {|num|
  puts (if num % 15 == 0
   "fizzbuzz"
  elsif num % 3 == 0
    "fizz"
  elsif num % 5 == 0
    "buzz"
  else
    num
  end)
}

しかしさらに、「putsの位置」について改善できます。

上記ではループ処理の中にputsを置いているのですが、それより以下の書き方では「処理の結果(戻り値)を出力している」ことになるのでこちらの方がより良いとのことです。

puts (1..20).map {|num|
  if num % 15 == 0
   "fizzbuzz"
  elsif num % 3 == 0
    "fizz"
  elsif num % 5 == 0
    "buzz"
  else
    num
  end
}

パタトクカシーー

出題は言語処理100本ノックより「パタトクカシーー」問題です。
文字列の奇数番を取り出して「パトカー」と連結した文字列を取得します。

はじめに解いたコードはこちらなのですが、nilが出力されてしまってうまく出力ができませんでした😓

str = "パタトクカシーー"
p str.chars.each_slice(2) {print _1[0]}
# => パトカーnil

この原因はeach_slice(n) {|list| ... } -> nilのように、each_sliceの戻り値がnilを返すためでした。※
文頭にpを書いてしまっているため、この戻り値nilを拾って出力してしまっていました。

参考:リファレンスマニュアルEnumerable#each_slice

なので、こちらだったらいけますね。
putsは内部でto_sしてるいるのでnilは出力されません。

str = "パタトクカシーー"
puts str.chars.each_slice(2) {print _1[0]}
# => パトカー

しかしこれもまた、ブロック内部でprintをしてしまっているので、FizzBuzzの所に書いたように「戻り値を出力する」という方針でこちらの方が良いです↓

str = "パタトクカシーー"
puts str.chars.each_slice(2).map { _1[0] }.join
# => パトカー

※ちなみにeach_slice(n) -> Enumeratorのように、blockを持たなければeach_sliceの戻り値はEnumeratorになります。

感想

その場で解くにはちょうどいい問題の難易度で、人のコードと比較したり、すぐにレビューを受けられる&質問できるとても充実した勉強会でした! p・print・putsの挙動や適切な配置、Enumerator関連のメソッド(each_slice)がブロックを持つ時の戻り値がnilであることなど、学びもたくさんありました! また勉強会後の懇親会では、rubykaigiの直後だったこともありkaigi内の発表のことやruby3.0について技術的な話が聞けてとても楽しかったです!