20190915のGitに関する記事は4件です。

共有フォルダ教が支配する世界に転生した場合の対応案

前書き

この世界ではバージョン管理ソフトが世の中に出回り始めて20年以上、フリーでかつ信頼性も高くなったツールが流行りだして10年以上はたっています。

さすがにソースコードはバージョン管理をするところが多くなりましたが、ドキュメントや、その他重要なデータはいまだバージョン管理をしない世界をよく見かけます。

バージョン管理ツールを縛り共有フォルダでの管理を支持する、いや、もはや信仰している方々は、提案されるバージョン管理ツールの導入案を却下あるいは先延ばしにし続けてきました。
その結果として作業者がフォルダを誤操作で消したり、古いファイルを上書きしたりするという必然の結果(彼らにとっては想定外な結果)に遭遇し、時間とリソースを無駄にドブに捨てるという贅沢をし続けています。

今回は、不幸にも、そのような世界に転生してしまった場合に、どうすべきかを考えてみます。

なお、この記事はバージョン管理ソフトの使い方を広めて、みんなで協力して導入していく、模範的な話ではなく、ベストではないが、最悪が回避できたらラッキー程度の話になります。

バージョン管理ツールが流行り、10年以上たって導入できないのは導入できないなりの事情が存在するからです。

なぜバージョン管理ツールは導入されないか

なぜバージョン管理ツールが導入されないかは幾つかのパターンがあります。

  • バージョン管理ツールを使用する必要がないあるいは、使用できない
  • バージョン管理ツールを知らない
  • バージョン管理ツールは知っていても、使える人間が少ない
  • 『高度なIT管理』を実現している組織の場合
  • コミット履歴を残したくないケース

バージョン管理ツールを使用する必要がないあるいは、使用できない

状況としては2つ考えられます。
1つは管理対象の資源の重要性が低い場合です。
たとえば、一時的なファイルや、消えても大きな影響がない資産の場合です。

もう一つはバージョン管理をするのが難しい資産です。
たとえば、動画ファイルとか仮想マシンのイメージとかはファイルのサイズが大きすぎる上、簡単に差分を確認する方法がないため、バージョン管理の対象にむいてません。

こういった場合は、バージョン管理ツールを入れる必要はありません。

バージョン管理ツールを知らない

バージョン管理ツール自体を知らない人は少なくなってきてますが、バージョン管理でなにができるかを理解している人はいまだ少数です。

たとえば、昔からの人間の場合、バージョン管理ツールはソースコードや設定ファイルといったテキストしか管理できないと思っている人がいます。このため、ExcelやWordといったOffice文章等は差分の確認もできないし、バージョン管理をする意味がないと思い込んでいます。

これは、ひと昔前は事実でしたが、今現在は、ただの思い込みです。
TotoiseHGやTotoiseSVNなどに付属するDiffツールは標準でOffice文章の差分をとることができますし、WinMergeなどのDiffツールは様々なプラグインがサポートされており画像だろうが差分を見ることが可能になっています。

「知らないで導入しない」パターンは、他のパターンに比べて模範的な方向にもっていく可能性が微粒子レベルで存在します。

バージョン管理ツールは知っていても、使える人間が少ない

管理者レベルでバージョン管理ツールのメリットを知っていても、末端の作業者がバージョン管理ツールを使えないなら、結局は導入はできません。

バージョン管理を使えない人間が少数、または教育期間が十二分に取れる場合、練習用のリポジトリを用意してバージョン管理ツールの教育を行うのは有効です。このリポジトリはプロダクトコードと完全に切り離して、最悪、破壊させていい状況にしておいたほうが教育効果が高いです。

…とはいえ、バージョン管理の使い方を学んでこなかった人間を大量に雇用せざる得ない状況は、たいてい学徒動員的な末期戦になっていることが多いので、教育とかいう贅沢は許されない状況である場合が多いでしょう。

『高度なIT管理』を実現している組織の場合

いわゆる『高度なIT管理』を実現している組織の場合、あまりに『高度』すぎてインターネットの接続を遮断したり、サーバー管理を『完全で完璧な高度な専門集団』にしかやらせないという方針の場合があります。

この場合、サーバーを自由に立ててバージョン管理を行うということはできません。

そして『完全で完璧な高度な専門集団』を説得する、あるいは動いていただくために掛ける労力は、しばしば、数度の共有フォルダの破壊のリスクを許容するに値する労力になる場合があります。

コミット履歴を残したくないケース

過剰な管理ごっこをしている世界においては、その防衛反応として事実を隠す反応が発生します。

そういった防衛反応として、表ざたにできないバグをこっそり直したり、試験の不具合件数をいじったりするダークサイドに堕ちた中間管理職が生まれたりします。彼らにとって闇の技法を暴いてしまうバージョン管理システムというのは相性が悪いでしょう。

共有フォルダの破壊にそなえる

すべてを捨てて旅にでるのも一つの選択肢ですが、無駄な足掻きはいくつか考えました。
外部からの支援がほぼない状況で、共有フォルダの致命的な破壊に備える程度の案になります。

案1:自分でバックアップを取る

もっとも単純な案は1日に1回、夜中にでも共有フォルダの内容を自分のPCにコピーしておくことです。
この際、フォルダ名にタイムスタンプでもつけておいて、履歴としてつかえばよいでしょう。
Copyと同時に履歴を削除しておけば、バックアップフォルダの増加も抑えられると思います。

PowerShellで実現する場合は以下のようになります。

$server = '\\IEWIN7\share\test'
$user = 'IEWIN7\IEUser'
$password = ConvertTo-SecureString 'Passw0rd!' -AsPlainText -Force
$backup_root = 'C:\dev\ps\share\bk'

# 残す履歴の数
$backup_cnt = 10

if ((Test-Path $server) -eq $True) {
    Write-Host '接続....'
    $cred = New-Object System.Management.Automation.PSCredential($user, $password)
    $drive = New-PSDrive -Name "Z" -PSProvider FileSystem -Root $server -Credential $cred -ea Stop
    if ((Test-Path $server) -eq $False) {
        Write-Error "$server に接続できません"
        exit 1
    }
}

$time = Get-Date -Format "yyyyMMddHHmmss"
$backup_path = Join-Path $backup_root $time
Write-Host "BACKUP.... $backup_path "
Copy-Item -Literal $server -Destination $backup_path -Recurse

# 古いフォルダの削除
Get-ChildItem -Literal $backup_root | 
    Sort-Object LastWriteTime -Descending  | 
    Select-Object -Skip $backup_cnt | 
    foreach { 
        Write-Host "Delete..." $_.FullName
        $rmpath = $_.FullName
        $cmd = "rmdir /s/q `"$rmpath`""
        cmd /c $cmd
    }

この案の素晴らしいところは、『高度なIT管理』下でも、やれるということです。
問題点としては、すべてを単純にバックアップするため履歴を追うのは困難ですし、ディスク容量を無駄に消費します。

案2:分散型バージョン管理システムを共有フォルダに対して使用する

①と②で共有フォルダ中のファイルを分散バージョン管理システムの管理下に入れます。
③と④で共有フォルダのリポジトリをローカルにクローンすることでバックアップとします。

定期的に②のadd&commitと④のpullを実行することに共有フォルダで変更された内容が履歴管理され、その内容はバックアップフォルダに反映されます。
定期的に実行されるaddとcommitは共有フォルダに変化がなければ履歴が作成されません。

これにより更新されたファイル内容だけが履歴管理されるようになります。バージョン管理ツールがサポートする変更履歴の確認や、特定バージョンの差し戻しなどが容易に利用できるようになります。

この構成のデメリットは以下の通りです。

  • 誰が更新したかわからない
  • 共有フォルダ中に管理ファイルが置かれるためサイズが増える。
    • これは歴がすすむにつれサイズが増大していく。
      • どこかのタイミングで履歴の削除が必要になる
      • 共有サーバーのディスクサイズが制限されている環境では使えない

Gitでの実現例

Gitで上記の構成を実現するためのPowerShellのスクリプト例を以下に記載します。
なおバージョンは「git version 2.23.0.windows.1」となります。

$server = '\\IEWIN7\share\testgit'
$user = 'IEWIN7\IEUser'
$password = ConvertTo-SecureString 'Passw0rd!' -AsPlainText -Force
$backup_root = 'C:\dev\ps\share\gitbk'


if ((Test-Path $server) -eq $False) {
    Write-Host '接続....'
    $cred = New-Object System.Management.Automation.PSCredential($user, $password)
    $drive = New-PSDrive -Name "Z" -PSProvider FileSystem -Root $server -Credential $cred
    if ((Test-Path $server) -eq $False) {
        Write-Error "$server に接続できません"
        exit 1
    }
}

Push-Location $server

if ((Test-Path '.git') -eq $False) {
    # Gitフォルダではない
    Write-Host "Init.........."
    git init
}
git add --all
git commit -a -m "auto commit"
git gc
Pop-Location

# ローカルにコピー
Write-Host "BACKUP... $backup_root"
mkdir $backup_root -Force
Push-Location $backup_root
if ((Test-Path '.git') -eq $False) {
    # Gitフォルダではない
    $clone_remote = $server -replace '\\', '/'
    git clone $clone_remote .
}
git pull origin master

Pop-Location

容量の節約と効率化のため、Gitはときどき、緩いフォーマットのオブジェクトの中の幾つかを1つのバイナリファイルにパックします。緩いフォーマットのオブジェクトの場合、ちょっとだけ変更したファイルであってもそれぞれオブジェクトとして存在します。
しかし、パックをすることによりそれらをまとめて変更点だけを保持するようにします。

この処理は「git gc」コマンドを実行することで手動でも実現できます。
https://git-scm.com/book/ja/v2/Git%E3%81%AE%E5%86%85%E5%81%B4-Packfile

この処理を行わないと共有サーバーのファイルサイズが更新のたびに想定以上に増大していくことになります。

共有フォルダでGit管理外の名前の変更が発生した場合、AddとCommit時に変更前のオブジェクトと履歴を紐づけることができるようです。

Mercurialの実装例

Gitで上記の構成を実現するためのPowerShellのスクリプト例を以下に記載します。
なおバージョンは「Mercurial - 分散構成管理ツール(バージョン 4.9.1)」となります。

$server = '\\IEWIN7\share\testhg'
$user = 'IEWIN7\IEUser'
$password = ConvertTo-SecureString 'Passw0rd!' -AsPlainText -Force
$backup_root = 'C:\dev\ps\share\hgbk'


if ((Test-Path $server) -eq $False) {
    Write-Host '接続....'
    $cred = New-Object System.Management.Automation.PSCredential($user, $password)
    $drive = New-PSDrive -Name "Z" -PSProvider FileSystem -Root $server -Credential $cred
    if ((Test-Path $server) -eq $False) {
        Write-Error "$server に接続できません"
        exit 1
    }
}

Push-Location $server

if ((Test-Path '.hg') -eq $False) {
    # HGフォルダではない
    Write-Host "Init.........."
    hg init
}
hg add
hg commit -A -m "auto commit"

Pop-Location

# ローカルにコピー
Write-Host "BACKUP... $backup_root"
mkdir $backup_root -Force
Push-Location $backup_root
if ((Test-Path '.hg') -eq $False) {
    # HGフォルダではない
    $clone_remote = $server -replace '\\', '/'
    hg clone $clone_remote .
}
hg pull default
hg update

Pop-Location

Gitと違い、MercurialはGCの必要がありません。
共有フォルダでMercurial管理外の名前の変更が発生した場合、Gitとことなり変更前のオブジェクトと履歴を紐づけはできないようです。

容量増加の検証

どちらのバージョン管理ツールを使用しても共有フォルダ内に履歴の管理情報を保持する必要があります。
ここで発生する懸念としては、共有フォルダのサイズがどの程度増量するかです。

今回は以下の条件で検証しました。

(1)下記のスクリプトを用いて任意のフォルダに1000行x251列のエクセルファイル(1MB前後)を100個作成する。


    Param(
        [String]$target_dir,
        [Int]$count,
        [Int]$rowcnt,
        [Int]$colcnt
    )

    function create_xls($app, [String]$path, [String[]]$ary, $rowcnt) {
        Write-Host $path
        $books = $app.Workbooks
        $book = $books.Add()
        $sheets = $book.Sheets
        $sheet = $sheets["Sheet1"]
        $cells = $sheet.Cells

        $fromCell = $cells[1,1]
        $toCell = $cells[$rowcnt,$ary.Length]
        $rng = $sheet.Range($fromCell, $toCell)
        $rng.Value = $ary
        [System.Runtime.Interopservices.Marshal]::ReleaseComObject($fromCell) | Out-Null
        [System.Runtime.Interopservices.Marshal]::ReleaseComObject($toCell) | Out-Null
        [System.Runtime.Interopservices.Marshal]::ReleaseComObject($rng) | Out-Null

        $book.SaveAs($path)
        $book.Close()

        [System.Runtime.Interopservices.Marshal]::ReleaseComObject($cells) | Out-Null
        [System.Runtime.Interopservices.Marshal]::ReleaseComObject($sheet) | Out-Null
        [System.Runtime.Interopservices.Marshal]::ReleaseComObject($sheets) | Out-Null
        [System.Runtime.Interopservices.Marshal]::ReleaseComObject($book) | Out-Null
        [System.Runtime.Interopservices.Marshal]::ReleaseComObject($books) | Out-Null

        $cell = $null
        Remove-Variable cell -ErrorAction SilentlyContinue
        $cells = $null
        Remove-Variable cells -ErrorAction SilentlyContinue
        $sheet = $null
        Remove-Variable sheet -ErrorAction SilentlyContinue
        $sheets = $null
        Remove-Variable sheets -ErrorAction SilentlyContinue
        $book = $null
        Remove-Variable book -ErrorAction SilentlyContinue
        $books = $null
        Remove-Variable books -ErrorAction SilentlyContinue


        [System.GC]::Collect()
        [System.GC]::WaitForPendingFinalizers()
        [System.GC]::Collect()

    }


    $app = New-Object -ComObject Excel.Application
    $app.DisplayAlerts = $False
    for ($i = 0; $i -lt $count; $i++) {
        $name = "test" + $i + ".xlsx"
        $path = Join-Path $target_dir $name
        $t = Get-Date -Format "yyyyMMddHHmmss"
        $ary = @($t)
        for ($j = 0; $j -lt $colcnt; $j++) {
            $ary += Get-Random 10000
        }
        create_xls $app $path $ary $rowcnt
    }
    $app.Quit();

    [System.Runtime.Interopservices.Marshal]::ReleaseComObject($app) | Out-Null
    $app = $null
    Remove-Variable app -ErrorAction SilentlyContinue

    [System.GC]::Collect()
    [System.GC]::WaitForPendingFinalizers()
    [System.GC]::Collect()

(2)下記のスクリプトを用いて1列目の全行を全て変更して保存します。

    Param(
        [String]$target_dir,
        [Int]$count,
        [Int]$rowcnt
    )

    function update_xls($app, [String]$path, [String]$time, $rowcnt) {
        Write-Host $path
        $books = $app.Workbooks
        $book = $books.Open($path)
        $sheets = $book.Sheets
        $sheet = $sheets["Sheet1"]
        $cells = $sheet.Cells

        $fromCell = $cells[1,1]
        $toCell = $cells[$rowcnt,1]
        $rng = $sheet.Range($fromCell, $toCell)
        $rng.Value = $time
        [System.Runtime.Interopservices.Marshal]::ReleaseComObject($fromCell) | Out-Null
        [System.Runtime.Interopservices.Marshal]::ReleaseComObject($toCell) | Out-Null
        [System.Runtime.Interopservices.Marshal]::ReleaseComObject($rng) | Out-Null

        $book.SaveAs($path)
        $book.Close()

        [System.Runtime.Interopservices.Marshal]::ReleaseComObject($cells) | Out-Null
        [System.Runtime.Interopservices.Marshal]::ReleaseComObject($sheet) | Out-Null
        [System.Runtime.Interopservices.Marshal]::ReleaseComObject($sheets) | Out-Null
        [System.Runtime.Interopservices.Marshal]::ReleaseComObject($book) | Out-Null
        [System.Runtime.Interopservices.Marshal]::ReleaseComObject($books) | Out-Null

        $cell = $null
        Remove-Variable cell -ErrorAction SilentlyContinue
        $cells = $null
        Remove-Variable cells -ErrorAction SilentlyContinue
        $sheet = $null
        Remove-Variable sheet -ErrorAction SilentlyContinue
        $sheets = $null
        Remove-Variable sheets -ErrorAction SilentlyContinue
        $book = $null
        Remove-Variable book -ErrorAction SilentlyContinue
        $books = $null
        Remove-Variable books -ErrorAction SilentlyContinue


        [System.GC]::Collect()
        [System.GC]::WaitForPendingFinalizers()
        [System.GC]::Collect()

    }


    $app = New-Object -ComObject Excel.Application
    $app.DisplayAlerts = $False
    for ($i = 0; $i -lt $count; $i++) {
        $name = "test" + $i + ".xlsx"
        $path = Join-Path $target_dir $name
        $time = Get-Date -Format "yyyyMMddHHmmss"
        update_xls $app $path $time $rowcnt
    }
    $app.Quit();

    [System.Runtime.Interopservices.Marshal]::ReleaseComObject($app) | Out-Null
    $app = $null
    Remove-Variable app -ErrorAction SilentlyContinue

    [System.GC]::Collect()
    [System.GC]::WaitForPendingFinalizers()
    [System.GC]::Collect()

(3)(2)の実行後、コミットし、コミット後のフォルダのサイズを調べます。これを10回繰り返します

Gitの場合

$src_dir = "C:\dev\ps\share\kensyou\test_dir"
$target_dir = "C:\dev\ps\share\kensyou\git"
$script_dir = Split-Path $MyInvocation.MyCommand.Path

function gettime() {
    return Get-Date -Format "yyyyMMddHHmmss"
}

mkdir $target_dir -force | Out-Null
Copy-Item $($src_dir+"\*") $target_dir


push-location $target_dir
$time = gettime
Write-Host "$time   構成管理前"    (Get-ChildItem -LiteralPath $target_dir -Recurse -Force | Measure-Object -Sum Length).Sum

git init | Out-Null
$time = gettime
Write-Host "$time   init後"    (Get-ChildItem -LiteralPath $target_dir -Recurse -Force | Measure-Object -Sum Length).Sum

for ($i = 1; $i -le 10; $i++) {
    if ($i -ne 1) {
        $script = Join-Path $script_dir "update_data.ps1"
        powershell -file $script  $target_dir 100 1000 250  | Out-Null
    }
    git add --all | Out-Null
    git commit -a -m "auto commit" | Out-Null

    $time = gettime
    Write-Host "$time   $i 回目(commit後)"   (Get-ChildItem -LiteralPath $target_dir -Recurse -Force | Measure-Object -Sum Length).Sum
    git gc | Out-Null

    $time = gettime
    Write-Host "$time   $i 回目(gc後)"   (Get-ChildItem -LiteralPath $target_dir -Recurse -Force | Measure-Object -Sum Length).Sum
}
pop-location

Mercurialの場合

$src_dir = "C:\dev\ps\share\kensyou\test_dir"
$target_dir = "C:\dev\ps\share\kensyou\hg"
$script_dir = Split-Path $MyInvocation.MyCommand.Path

function gettime() {
    return Get-Date -Format "yyyyMMddHHmmss"
}

mkdir $target_dir -force | Out-Null
Copy-Item $($src_dir+"\*") $target_dir


push-location $target_dir
$time = gettime
Write-Host "$time   構成管理前"    (Get-ChildItem -LiteralPath $target_dir -Recurse -Force | Measure-Object -Sum Length).Sum

hg init | Out-Null
$time = gettime
Write-Host "$time   init後"    (Get-ChildItem -LiteralPath $target_dir -Recurse -Force | Measure-Object -Sum Length).Sum

for ($i = 1; $i -le 10; $i++) {
    if ($i -ne 1) {
        $script = Join-Path $script_dir "update_data.ps1"
        powershell -file $script  $target_dir 100 1000 250  | Out-Null
    }
    hg add | Out-Null
    hg commit -A -m "auto commit" | Out-Null

    $time = gettime
    Write-Host "$time   $i 回目"  (Get-ChildItem -LiteralPath $target_dir -Recurse -Force | Measure-Object -Sum Length).Sum
}
pop-location

計測結果

Gitの結果

時刻 経過時間 説明 バイト数 変化バイト数
12:40:42 構成管理前 108,224,999
12:40:42 0:00:00 init後 108,240,786 15,787
12:40:46 0:00:04 1回目(commit後) 167,167,147 58,926,361
12:41:03 0:00:17 1回目(gc後) 160,253,884 -6,913,263
12:42:38 0:01:35 2回目(commit後) 223,790,889 63,537,005
12:43:00 0:00:22 2回目(gc後) 217,598,218 -6,192,671
12:44:38 0:01:38 3回目(commit後) 279,699,191 62,100,973
12:45:13 0:00:35 3回目(gc後) 272,927,795 -6,771,396
12:46:55 0:01:42 4回目(commit後) 335,808,640 62,880,845
12:47:22 0:00:27 4回目(gc後) 329,101,286 -6,707,354
12:49:06 0:01:44 5回目(commit後) 391,427,905 62,326,619
12:49:44 0:00:38 5回目(gc後) 384,702,914 -6,724,991
12:51:50 0:02:06 6回目(commit後) 447,154,138 62,451,224
12:52:57 0:01:07 6回目(gc後) 440,272,643 -6,881,495
12:54:51 0:01:54 7回目(commit後) 502,722,692 62,450,049
12:55:25 0:00:34 7回目(gc後) 495,815,728 -6,906,964
12:57:07 0:01:42 8回目(commit後) 558,031,925 62,216,197
12:57:42 0:00:35 8回目(gc後) 551,140,571 -6,891,354
12:59:24 0:01:42 9回目(commit後) 613,900,051 62,759,480
13:00:01 0:00:37 9回目(gc後) 607,291,311 -6,608,740
13:01:35 0:01:34 10回目(commit後) 669,533,780 62,242,469
13:02:13 0:00:38 10回目(gc後) 662,895,263 -6,638,517

Mercurialの結果

時刻 経過時間 説明 バイト数 変化バイト数
13:06:28 構成管理前 108,224,999
13:06:30 0:00:02 init後 108,225,115 116
13:06:40 0:00:10 1回目 160,990,957 52,765,842
13:08:27 0:01:47 2回目 217,741,339 56,750,382
13:10:11 0:01:44 3回目 274,194,265 56,452,926
13:12:06 0:01:55 4回目 330,463,591 56,269,326
13:14:03 0:01:57 5回目 386,622,952 56,159,361
13:16:20 0:02:17 6回目 442,364,652 55,741,700
13:18:28 0:02:08 7回目 498,348,170 55,983,518
13:20:14 0:01:46 8回目 554,076,263 55,728,093
13:22:02 0:01:48 9回目 609,921,087 55,844,824
13:24:05 0:02:03 10回目 666,193,613 56,272,526

ファイルのサイズとしては「GC後のGitのサイズ<Mercurialサイズ<GC前のGitのサイズ」となります。
10回程度のコミットで6倍のサイズになっています。

ファイルの更新~コミット&GCの時間はGitとMercurialに大きな差はないように見えます。
※時間の注意としては、100ファイルに対して列を書き換える時間も含まれていることに注意してください。

どう実行すべきか

add & commitのタイミングはとりあえず1日単位でおこなって共有フォルダのサイズの増加量を監視して調整したほうがよさそうです。

また、更新頻度にもよりますが、バージョン管理の情報が肥大化することが予想されます。
そのため、どのタイミングで履歴を捨てるかを検討した方がいいです。
(たとえばリリースが行われた時点で開発中の履歴を捨てる等)

案3 ローカルにコピーしたバックアップフォルダに対して分散バージョン管理を使用する

案1のやりかたでローカルに共有フォルダの内容を定期的にコピーします。
ただし、フォルダ毎にわけるのでなく、上書き保存として、保存後、コミットをします。

このメリットは共有フォルダのサイズの肥大化を防げることです。
デメリットとしては、その履歴の活用が個人でしか行えないことです。

参考

Powershellで共有フォルダにアクセス
https://qiita.com/mugippoi/items/608255dee3fc9d3b35dc

Gitの内側
https://git-scm.com/book/ja/v2/Git%E3%81%AE%E5%86%85%E5%81%B4-%E9%85%8D%E7%AE%A1%EF%BC%88Plumbing%EF%BC%89%E3%81%A8%E7%A3%81%E5%99%A8%EF%BC%88Porcelain%EF%BC%89

まとめ

今回は共有フォルダ教が支配する世界に転生した場合の対応案を考えてみました。

おそらくは最悪の状況を回避するという手としては使えないこともないでしょうが、転生しなおした方が楽だと思います。

(補足)
なお、まじめにまっとうなバージョン管理の導入を考えるなら以下で紹介している「パターンによるソフトウェア構成管理」を基に現場にあった論を組み立てた方がいいと思います。

古典を読む~パターンによるソフトウェア構成管理
https://qiita.com/mima_ita/items/3213998ef17216e8c775

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

rebaseでcommitが消えた…?

内容

commitrebaseされず、想定の結果にならなかったので、事象や解決策などをまとめました。
rebaseの挙動についてはこの記事が参考になります。

やりたかったこと

masterfeature/Arebaseしたかった。

git rebase master feature/A

実行結果

あれ…feature/Acommitrebaseされていない…

Before

* commit 414b95af83ab984b20147e0094ed8eade31f348b (origin/master, master)
| Author: ****
| Date:   Sat Sep 14 22:02:50 2019 +0900
|
|     D
|
| * commit 12b5e389f870e565beb195e6b9deda974daacac0 (HEAD -> feature/A, origin/feature/A)
| | Author: ****
| | Date:   Sat Sep 14 21:43:10 2019 +0900
| |
| |     C
| |
| * commit 53af224f9c0f1c326dbdd20efc85edd04fe84e36
| | Author: ****
| | Date:   Sat Sep 14 19:37:50 2019 +0900
| |
| |     B
| |
| * commit 650053d9adbaa1b33a3a6b72504cbcad9ff23eb9
|/  Author: ****
|   Date:   Sat Sep 14 19:37:35 2019 +0900
|
|       A
|
* commit 78369ea68187c501d4b3fa7b453a9ce3eea95ca1
  Author: ****
  Date:   Sat Sep 14 19:35:28 2019 +0900

      first commit

After

* commit 414b95af83ab984b20147e0094ed8eade31f348b (HEAD -> feature/A, origin/master, master)
| Author: ****
| Date:   Sat Sep 14 22:02:50 2019 +0900
|
|     D
|
| * commit 12b5e389f870e565beb195e6b9deda974daacac0 (origin/feature/A)
| | Author: ****
| | Date:   Sat Sep 14 21:43:10 2019 +0900
| |
| |     C
| |
| * commit 53af224f9c0f1c326dbdd20efc85edd04fe84e36
| | Author: ****
| | Date:   Sat Sep 14 19:37:50 2019 +0900
| |
| |     B
| |
| * commit 650053d9adbaa1b33a3a6b72504cbcad9ff23eb9
|/  Author: ****
|   Date:   Sat Sep 14 19:37:35 2019 +0900
|
|       A
|
* commit 78369ea68187c501d4b3fa7b453a9ce3eea95ca1
  Author: ****
  Date:   Sat Sep 14 19:35:28 2019 +0900

      first commit

問題点

実はfeature/Acommitは全て空コミットでした、
デフォルトだと空コミットrebaseの際に削除されてしまう。

解決策

rebase時に--keep-emptyオプションを指定する。

git rebase --keep-empty master feature/A

実行結果

想定通りの結果になりました!

After

* commit 1bb327740bf6cb6e25e0afb6500ed9c1020503c0 (HEAD -> feature/A)
| Author: ****
| Date:   Sat Sep 14 21:43:10 2019 +0900
|
|     C
|
* commit 5bb9c9e4e943deccbb5e7ddeb4ac07caf158aeb9
| Author: ****
| Date:   Sat Sep 14 19:37:50 2019 +0900
|
|     B
|
* commit ad32378d763b315b419a86f4303f61f147886c11
| Author: ****
| Date:   Sat Sep 14 19:37:35 2019 +0900
|
|     A
|
* commit 414b95af83ab984b20147e0094ed8eade31f348b (origin/master, master)
| Author: ****
| Date:   Sat Sep 14 22:02:50 2019 +0900
|
|     D
|
| * commit 12b5e389f870e565beb195e6b9deda974daacac0 (origin/feature/A)
| | Author: ****
| | Date:   Sat Sep 14 21:43:10 2019 +0900
| |
| |     C
| |
| * commit 53af224f9c0f1c326dbdd20efc85edd04fe84e36
| | Author: ****
| | Date:   Sat Sep 14 19:37:50 2019 +0900
| |
| |     B
| |
| * commit 650053d9adbaa1b33a3a6b72504cbcad9ff23eb9
|/  Author: ****
|   Date:   Sat Sep 14 19:37:35 2019 +0900
|
|       A
|
* commit 78369ea68187c501d4b3fa7b453a9ce3eea95ca1
  Author: ****
  Date:   Sat Sep 14 19:35:28 2019 +0900

      first commit

深掘り

rebaseは、

  1. 移行元のcommitを一時退避する。
  2. 移行先のブランチに移動する。
  3. 移行先に1.で一時退避したcommitを適用

という動きをするようです。
rebaseのデフォルトの動きは、3.でファイル差分が存在しない時はcommitしない挙動のようです。

追加で気になったこと

cherry-pickで空コミットを適用しようとした時はどうなるのか。

やってみたこと

cherry-pickで空コミットを取り込んでみました。

git cherry-pick 414b95af83ab984b20147e0094ed8eade31f348b

実行結果

commitできるものがないよ〜。
とにかくcommitしたかったら空コミットしてね。
それがイヤだったらresetしてね」
と言っているようです。

On branch feature/A
You are currently cherry-picking commit 414b95a.

nothing to commit, working tree clean
The previous cherry-pick is now empty, possibly due to conflict resolution.
If you wish to commit it anyway, use:

    git commit --allow-empty

Otherwise, please use 'git reset'

解決策

cherry-pick--allow-emptybgitオプションを指定しましょう。

git cherry-pick --allow-emptybgit 414b95af83ab984b20147e0094ed8eade31f348b

まとめ

  • rebase時に--keep-emptyオプションを指定しないと空コミットがrebaseされない。
  • cherry-pick時に--allow-emptybgitオプションを指定しないと手動で別作業が必要になる。

今回の事象がgithubのプルリクで問題になるケースの記事を見つけたので参考にしてください。

参考記事

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

初心者用Github環境構築

初心者用Github環境構築

1.砂場(リモート)にため池(リポジトリ)を作ろう!

※リポジトリ(リモートの保存場所)を作成しない場合はスキップする。
※実際の砂場にため池を作る行為は器物破損に問われる場合があります。

1ー1.https://github.com にアクセスする。

1ー2.右上の『+』、『New repository』

1ー3.リポジトリ情報を入力し、『Create Repository』

Owner ... リポジトリの所有者
Repository Name ... リポジトリ名
Discription ... 説明
Public/Private ... 公開(Plublic)か、非公開(Private)か。公開にする場合も招待を受けた人しか編集できない。
Add .ignore ... ファイル管理除外設定ファイルの言語。その言語で管理しないファイルの既定を定義する。(Swift言語にしました)

1ー4.ため池(リポジトリ)完成

砂場(リモート)にため池(リポジトリ)ができました!

2.砂場(リモート)のため池(リポジトリ)に編集者を招待しよう!

※リポジトリ(リモートの保存場所)を管理しない場合はスキップする。

2ー1.『Collaborators』、『Settings』

2ー2.招待するGitアカウント名を入力し、『Add collaborator』

(所有者は招待不要)

3.所有者と編集者の身元を保証しよう!

※これによりパスワード入力を回避できる。

3ー1.身元保証するアカウントでGithubにログインする。

3ー2.右上の『+』、『Settings』、『SSH and GPG keys』

3ー3.『New SSH Key』

3ー4.SSH秘密鍵と公開鍵を作成する。

ssh-keygen
mkdir ~/.ssh
cd ~/.ssh
ssh-keygen
#[Enter]
#[Enter]
#[Enter]
cat id_rsa.pub

3ー3.SSH公開キー(id_rsa.pub)を設定する。『Add SSH Key』

4.リポジトリのURLを確認する。

4ー1.『Use SSH』、コピーアイコン

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Herokuにデプロイをする時に詰まった箇所について

はじめに

RailsアプリケーションをHerokuにデプロイする際、自分が詰まった箇所について説明します。
また、herokuへのデプロイ方法は、最終行に記載した<4.デプロイ手順の参考記事>を参考にデプロイをしました。

それぞれのバージョンは以下の通りです。
MacOS 10.14.5
Ruby 2.5.1p57
Rails 5.2.3
heroku/7.26.2 darwin-x64 node-v11.14.0

画像
https://gyazo.com/847aa4661ae3b2f278ae2328d33d32dd

1.Herokuにログインする時

①現象

以下のように、herokuにログインするコマンドを実行しても、

$heroku login 

メールアドレスおよびパスワードを入力する画面いならない(おそらく、このような方法でログインすることもできるのだろう)。

動画(見えづらくて申し訳ありません(>人<;))
https://gyazo.com/e3873154a60890c7fa5aab07146c962c

②解決方法

以下のようなコマンドを入力すれば、解決できます。

$ heroku login --interactive

動画
https://gyazo.com/4829936b6a6626a7ea229383eecbeae9

③参考記事

https://teratail.com/questions/158638

2.Railsの設定

(1).Gemfileの設定

本番環境用にGemfileの設定しておかないと、以下のようなエラーが発生します。
(今回は、自分がアプリで使用したdeviseおよびjquery-railsの2つのgemを例にあげます。)

①エラー現象

以下のような画面が表示され、デプロイが失敗する。

deviseの場合、

remote:        Caused by:
remote:        NameError: uninitialized constant Devise

画像
https://gyazo.com/eb6608754ad0937c550eb45acdf848b5

jquery-railsの場合、

remote:        rake aborted!
remote:        Sprockets::FileNotFound: couldn't find file 'jquery' with type 'application/javascript'

画像
https://gyazo.com/71cc267fe306d82fdf6e65df2b2288a1

②解決方法

Gemfileの一番下に、以下のような設定をすると解決します(deviseおよびjquery-rails)。

group :development, :production, :test do
  gem 'devise'
  gem 'jquery-rails'
end

画像
https://gyazo.com/625adb044cecaff749c60f4f77fb317a

(2).テンプレートリテラル記法を用いる場合のproduction.rbの設定

①エラー現象

以下のようなエラー画面が表示され、デプロイが失敗する。

remote:        rake aborted!
remote:        Uglifier::Error: Unexpected character '`'

下の方に赤文字で以下のような表示がされる。

remote:  !     Precompiling assets failed.

全体の画像
https://gyazo.com/37b8b091eda6cec2c0514b54dea5a5c5

②解決方法

config/environments/production.rbにある以下のコードをコメントアウトする。

# config.assets.js_compressor = :uglifier

自分の場合、production.rbの26行目に書いてありました。
https://gyazo.com/b66d6c1a13d7b22a5105f88e1303f40d

そもそも、テンプレートリテラル記法とは?
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/template_strings

3.デプロイ成功後

(1).デプロイは成功後にアクセスするURLについて

①現象

herokuへのデプロイが成功すると、以下のような長いログの画面が表示される
https://gyazo.com/2d2e4ea40e66373aa6b62cb7df7f15cc
(画面が青い理由は、どこからどこまでがデプロイのログなのかを、自分で判断するためです。見えづらくて申し訳ありません(>人<;))。

そして、以下のような画面が表示される。

$ git push heroku master
 ・
 ・
 ・
remote: -----> Compressing...
remote:        Done: 36.9M
remote: -----> Launching...
remote:        Released v6
remote:        https://enigmatic-reaches-21915.herokuapp.com/ deployed to Heroku
remote: 
remote: Verifying deploy... done.
To https://git.heroku.com/enigmatic-reaches-21915.git
 * [new branch]      master -> master

そして、同時にherokuへのURLが生成されます。

remote:        Released v6
remote:        https://enigmatic-reaches-21915.herokuapp.com/ deployed to Heroku
remote: 
remote: Verifying deploy... done.
To https://git.heroku.com/enigmatic-reaches-21915.git
 * [new branch]      master -> master

②解決方法

アクセスできるURLは以下のような表示がされている方にアクセスすると解決します。

remote:        https://<個別のアプリの名前>.herokuapp.com/ deployed to Heroku

※自分は、今回デプロイするときのアプリの名前を指定してなかったので、

enigmatic-reaches-21915

という名前になっています。

③参考記事

https://qiita.com/kodai_0122/items/a1f01b18bb3e0ddde62a

(2).デプロイは成功し、正しいURLにアクセスしたが、エラー画面が表示される問題

①エラー現象

正しいURLにアクセスした時、以下のようなエラー画面が表示される。

"We're sorry, but something went wrong."

画像
https://gyazo.com/3a6a0c733d87efe0866c2e3f8d585a0f

②解決方法

デプロイをした後に、本番環境においてマイグレーションを行えば、解決できます。

③参考記事

https://i.gyazo.com/3a6a0c733d87efe0866c2e3f8d585a0f.png
https://qiita.com/KazuhoE/items/06d13ccd4c72fd31c5d9
https://teratail.com/questions/124183

4.デプロイ手順の参考記事

https://qiita.com/NaokiIshimura/items/eee473675d624a17310f
https://qiita.com/kazukimatsumoto/items/a0daa7281a3948701c39
https://sweets-engineer.com/heroku/#heroku
https://qiita.com/fuwamaki/items/f7752eb7a2727660239f

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む