1. ホーム
  2. git

[解決済み] PerforceユーザーのためのGit

2023-05-01 11:53:38

質問

私は何年もの間 Perforce を使ってきました。私は個人的なコードに git を使用するように切り替えたいと思っていますが、私が見たすべての git チュートリアルは、あなたが完全なソース管理の初心者であることを前提としているか (これは信じられないほど面倒です)、または svn に慣れている (私はそうではありません) ことになっています。

私は p4 を知っていますし、分散型ソース管理システムの背後にある考え方も理解しています (だから、売り込みは必要ありません。ありがとうございます)。私が欲しいのは、p4 コマンドから同等の git コマンドへの翻訳表と、p4 に相当するものがない "can't live without" コマンドです。

すべての p4 ユーザーは異なる p4 のサブセットを使用していると思うので、私が p4 で定期的に行っていることで、私が見たドキュメントからすぐに明らかにならない、git でできるようにしたいことをいくつか挙げてみます。

  1. 単一のクライアントで複数の保留中のチェンジリストを作成する。( p4 change )
  2. 保留中のチェンジリストを編集します。(また p4 change )
  3. 保留中のすべてのチェンジリストの一覧を見る ( p4 changes -s pending )
  4. 私のクライアントで変更されたすべてのファイルのリスト ( p4 opened ) あるいは保留中の変更リスト ( p4 describe )
  5. 保留中の変更リストの diff を見る (私は、このためにラップスクリプトを使っています。 p4 diffp4 describe )
  6. 与えられたファイルについて、どの提出されたチェンジリストがどの行に影響したかを見る ( p4 annotate )
  7. は、与えられたファイルに対して、そのファイルに影響を与えたチェンジリストの説明の一覧を見ることができます ( p4 log )
  8. 保留中のチェンジリストを提出する ( p4 submit -c )
  9. 保留中のチェンジリストを中止する ( p4 revert )

これらの多くは、"changelist" を中心に展開されます。 "changelist" は p4 の用語です。git の同等の用語は何ですか?

p4がchangelistと呼ぶものの代わりに、gitユーザが使うのはbranchesのようですね。p4 にもブランチと呼ばれるものがありますが、これらは漠然とした関連概念に過ぎないようなので、少し混乱しています。(私はいつも p4 のブランチの概念はかなり奇妙だと思っていましたが、古典的な RCS のブランチの概念とはまた違っています)。

とにかく... p4 の changelist で通常行っていることを、git の branches でどのように実現すればよいのかわかりません。p4ではこのようなことができます。

$ p4 edit a.txt
$ p4 change a.txt
Change 12345 created.

この時点で、私は a.txt を含む changlist を持っています。私は説明を編集して、変更リストを提出することなく作業を続けることができます。また、コードの他のレイヤーのバグ修正のように、他のファイルにいくつかの変更を加える必要があることが判明した場合、私は同じクライアントでそれを行うことができます。

$ p4 edit z.txt
$ p4 change z.txt
Change 12346 created.

これで、同じクライアントに 2 つの別々の変更リストができました。私はこれらを同時に作業することができ、それらの間を "switch" するために何もする必要がありません。コミットするとき、私はそれらを別々に提出することができます。

$ p4 submit -c 12346  # this will submit the changes to z.txt
$ p4 submit -c 12345  # this will submit the changes to a.txt

これをgitで再現する方法がわかりません。私の実験では、どうやら git add は現在のブランチと関連付けられていないようです。私が知る限りでは、私が git commit を実行すると、そのブランチにあるすべてのファイルがコミットされます。 git add -にあるすべてのファイルをコミットします。

$ git init
Initialized empty Git repository in /home/laurence/git-playground/.git/
$ ls
a.txt  w.txt  z.txt
$ git add -A .
$ git commit
 Initial commit.
 3 files changed, 3 insertions(+), 0 deletions(-)
 create mode 100644 a.txt
 create mode 100644 w.txt
 create mode 100644 z.txt
$ vi a.txt z.txt 
2 files to edit
$ git status
# On branch master
# Changed but not updated:
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#   modified:   a.txt
#   modified:   z.txt
#
no changes added to commit (use "git add" and/or "git commit -a")
$ git branch aardvark
$ git checkout aardvark
M   a.txt
M   z.txt
Switched to branch 'aardvark'
$ git add a.txt 
$ git checkout master
M   a.txt
M   z.txt
Switched to branch 'master'
$ git branch zebra
$ git checkout zebra
M   a.txt
M   z.txt
Switched to branch 'zebra'
$ git add z.txt 
$ git status
# On branch zebra
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#   modified:   a.txt
#   modified:   z.txt
#
$ git checkout aardvark
M   a.txt
M   z.txt
Switched to branch 'aardvark'
$ git status
# On branch aardvark
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#   modified:   a.txt
#   modified:   z.txt

この例では、aardvark と zebra ブランチはまったく同じ変更を含んでいるように見えます。 git status の出力から、どちらでコミットしても同じ効果があるように見えます。私は何か間違ったことをしているのでしょうか?

どのように解決するのですか?

私はあまり perforce を使ったことがないので、これは正確には 1:1 の翻訳ではないかもしれません。また、git や mercurial のような分散型ソース管理システムは、いずれにせよ異なるワークフローを持っているので、本当に 1:1 の翻訳はありません (そうあるべきでもありません)。とにかく、ここに行きます。

  • 複数の保留中のチェンジリストを作成する -> 代わりにブランチを使用します。gitのブランチは軽くて速く、作成に1秒かからず、マージにも通常2秒かかりません。ブランチを恐れず、頻繁にリベースを行いましょう。

    git branch new-branch-name
    git checkout new-branch-name
    
    
    

    または1行で済ませる。

    git checkout -b new-branch-name
    
    
  • 保留中のチェンジリストの一覧を見る -> 複数の保留中のチェンジリストに相当するのは複数のブランチなので、ブランチを見るだけでよいです。

    git branch
    
    

    リモートブランチも表示したい場合。

    git branch -a
    
    

    どのブランチがマージ待ちで、どれがすでにマージされたかを追跡する必要がないように、マージが成功したらすぐにブランチを削除するのが良い習慣と考えられています。

  • すべての変更されたファイルをリストアップする -> 特定のブランチにおける単一の保留中の "changelist" に対して、git はインデックスまたはキャッシュという概念を持っています。変更をコミットするためには、まずこのインデックスにファイルを追加する必要があります。これにより、どのファイル群がひとつの変更を表すのかを手動で選択したり、無関係なファイルを無視したりすることができます。どのファイルがインデックスに追加されたか、あるいはされていないかの状態を見るには、次のようにします。

    git status
    
    
  • 保留中の変更リストの差分を見る -> これには2つの部分があります。まず、作業ディレクトリとインデックスのdiffを見ること。

    git diff
    
    

    しかし、今入力しているものと最後のコミットとの差分を知りたいのであれば、作業ディレクトリ+インデックスとHEADとの差分を求めるのが本当のところでしょう。

    git diff HEAD
    
    
  • 与えられたファイルについて、どの提出されたチェンジリストがどの行に影響を与えたかを見る -> これは簡単です。

    git blame filename
    
    

    とか、ウィンドウズ環境であればなおさらです。

    git gui blame filename
    
    

    Git gui はファイルの解析に時間がかかりますが(Cではなくtclで書かれています)、コミットIDをクリックして過去に戻る "time travel" 機能など、たくさんのすてきな機能が備わっています。私はただ、あるバグが最終的にどのように解決されるかを知ることができるように、未来への "タイムトラベル" 機能が実装されることを望むだけです;-)。

  • 与えられたファイルについて、そのファイルに影響を与えたチェンジリストの説明の一覧を見る ->も簡単です。

    git log filename
    
    

    しかし、git log はこれよりもずっと強力なツールです。実際、私の個人的なスクリプトのほとんどは、リポジトリを読み込むために git log を利用しています。man ページを読んでみてください。

  • 保留中のチェンジリストを提出する -> これも簡単です。

    git commit
    
    

私の典型的なgitのワークフローについては、以前の質問に対する私の回答を参照してください。 Gitを学んでいます。自分が正しい道を歩んでいるかどうかを知る必要がある

私が説明したワークフローに従うなら、gitk のようなツールは、変更点のグループをはっきりと見ることができるので、より価値があることがわかると思います。


追加の回答です。

Git は非常に柔軟で、あなたが説明したことを行うにはいくつかの方法があります。覚えておくべきことは、作業中の各機能に対して常に新しいブランチを開始することです。これは、master ブランチには手を付けず、バグ修正のためにいつでもそこに戻ることができることを意味します。git での作業では、ほとんどの場合、新しいブランチを作成する必要があります。

git checkout -b new-feature-a

これで、ファイル a.txt を編集できるようになりました。他の機能と同時に作業するには、次のようにします。

git checkout master
git checkout -b new-feature-z

これで、ファイルz.txtを編集できるようになりました。a.txtに戻すには。

git checkout new-feature-a

しかし待ってください、new-feature-z に変更があり、git はブランチの切り替えを許可してくれません。このとき、あなたには二つの選択肢があります。ひとつは最も単純で、現在のブランチにすべての変更をコミットすることです。

git add .
git commit
git checkout new-feature-a

これは私が推奨する方法です。しかし、本当にコードをコミットする準備ができていないのであれば、一時的に隠しておくことができます。

git stash

これで new-feature-a ブランチに切り替えることができました。作業していたコードに戻るには、stashをポンと押すだけです。

git checkout new-feature-z
git stash pop

すべてが完了したら、すべての変更をmasterにマージバックします。

git merge --no-ff new-feature-a
git merge --no-ff new-feature-z

マージはとても速くて簡単なので(コンフリクトが起こるのはとても稀で、コンフリクトが起こったときの解決もそれほど難しくないので簡単)、私たちはあらゆることにgitのブランチを使用しています。

他のソースコントロールツール(おそらく mercurial を除く)では見られない、git におけるブランチの一般的な使用例をもう一つ紹介します。

開発環境を反映させるために設定ファイルを変更し続ける必要がありますか? それならブランチを使いましょう。

git checkout -b dev-config

次に、お気に入りのエディタで設定ファイルを編集し、変更をコミットしてください。

git add .
git commit

これで、すべての新しいブランチは master ではなく dev-config ブランチから開始できるようになりました。

git checkout dev-config
git checkout -b new-feature-branch

編集が終わったら、対話型リベースを使用して新機能ブランチからdev-configの編集を削除してください。

git rebase -i master

不要なコミットを削除して保存します。これで、カスタムの設定を編集していないクリーンなブランチができました。master にマージする時間です。

git checkout master
git merge --no-ff new-feature-branch
# because master have changed, it's a good idea to rebase dev-config:
git checkout dev-config
git rebase master

注意すべきは、編集を削除する際に git rebase -i による編集の削除は、すべての変更が同じファイルで起こった場合でも機能することに留意してください。Gitはファイルの内容*ではなく、変更を記憶しているのです。

*note: 実際には、技術的には完全に正しいわけではありませんが、ユーザーとしてはそのように感じます。


さらに追加で回答します。

あなたのコメントから、あなたは2つのブランチを同時に存在させ、結合されたコードがどのように動作するかをテストしたいようです。まあ、これはブランチのパワーと柔軟性を説明する良い方法です。

最初に、安価なブランチと変更可能な履歴がワークフローに及ぼす影響について一言。私が CVS と SVN を使用していたとき、私は常にコミットすることに少し抵抗がありました。それは、不安定なコードをコミットすると、必然的に他の人の作業中のコードを壊してしまうからです。しかし、gitを使うことで、そのような恐れはなくなりました。なぜなら、gitでは、私がmasterにマージするまで、他の人が私の変更を取得することはないからです。だから今は、5行書くごとにコードをコミットしている自分に気づきました。コミットするのに完璧な先見性は必要ありません。考え方を変えるだけです。commit-to-branch==add-to-changeset, merge-to-master==commit-changeset ですね。

では、例に戻りましょう。私ならこうします。たとえば、あるブランチ new-feature-z でテストしたいとします。 new-feature-a . 私なら、新しいブランチを作ってテストします。

# assume we are currently in branch new-feature-z
# branch off this branch for testing
git checkout -b feature-z-and-feature-a
# now temporarily merge new-feature-a
git merge --no-ff new-feature-a

これでテストができるようになりました。もし、feature-z を feature-a で動作させるために何かを修正する必要があれば、そうしてください。もしそうなら、その変更を関連するブランチにマージすることができます。使用方法 git rebase -i を使用して、無関係な変更をマージから削除してください。

あるいは、git rebase を使って new-feature-z のベースを一時的に変更し、new-feature-a を指すようにすることもできます。

# assume we are currently in branch new-feature-z
git rebase new-feature-a

これでブランチ履歴が変更され、new-feature-z は master ではなく new-feature-a を元にしたものになりました。これでテストができるようになりました。このブランチにコミットされた変更は、すべて new-feature-z ブランチに属することになります。new-feature-a を変更する必要がある場合は、ブランチに戻ってリベースすれば新しい変更が反映されます。

git checkout new-feature-a
# edit code, add, commit etc..
git checkout new-feature-z
git rebase new-feature-a
# now new-feature-z will contain new changes from new-feature-a

終了したら、new-feature-a からの変更を削除するために master にリベースするだけです。

# assume we are currently in branch new-feature-z
git rebase master

新しいブランチを始めることを恐れてはいけません。捨てるブランチを始めることを恐れてはいけない。ブランチを捨てることを恐れてはいけません。そして、merge=submit、commit=add-to-changeset なので、頻繁にコミットすることを恐れてはいけません。コミットは開発者にとって究極の取り消しツールであることを忘れないでください。

ああ、それからもうひとつ、git では削除したブランチはまだリポジトリに存在します。ですから、誤って削除してしまったものが後で有用であることに気づいたとしても、履歴を検索すればいつでもそれを取り戻すことができます。ですから、ブランチを削除することを恐れないでください。