Cabang Bernama vs Beberapa Repositori

130

Kami saat ini menggunakan subversi pada basis kode yang relatif besar. Setiap rilis mendapatkan cabang sendiri, dan perbaikan dilakukan terhadap bagasi dan dimigrasikan ke cabang rilis menggunakansvnmerge.py

Saya percaya waktunya telah tiba untuk beralih ke kontrol sumber yang lebih baik, dan saya telah bermain-main dengan Mercurial untuk sementara waktu.

Tampaknya ada dua sekolah pada mengelola struktur rilis seperti itu menggunakan Mercurial. Entah setiap rilis mendapatkan repo sendiri, dan perbaikan dilakukan terhadap cabang rilis dan didorong ke cabang utama (dan cabang rilis baru lainnya.) ATAU menggunakan cabang bernama dalam satu repositori (atau beberapa salinan yang cocok.)

Dalam kedua kasus itu sepertinya saya mungkin menggunakan sesuatu seperti transplantasi untuk mengubah perubahan untuk dimasukkan dalam cabang rilis.

Aku bertanya padamu; apa manfaat relatif dari masing-masing pendekatan?

James Emerton
sumber

Jawaban:

129

Perbedaan terbesar adalah bagaimana nama-nama cabang dicatat dalam sejarah. Dengan cabang bernama, nama cabang disematkan di setiap perubahan dan dengan demikian akan menjadi bagian abadi dari sejarah. Dengan klon tidak akan ada catatan permanen dari mana perubahan tertentu berasal.

Ini berarti bahwa klon sangat bagus untuk eksperimen cepat di mana Anda tidak ingin merekam nama cabang, dan cabang bernama bagus untuk cabang jangka panjang ("1.x", "2.x" dan yang serupa).

Perhatikan juga bahwa repositori tunggal dapat dengan mudah mengakomodasi beberapa cabang ringan di Mercurial. Cabang in-repositori seperti itu dapat di-bookmark sehingga Anda dapat dengan mudah menemukannya lagi. Katakanlah Anda telah mengkloning repositori perusahaan ketika terlihat seperti ini:

[a] --- [b]

Anda kembali dan membuat [x]dan [y]:

[a] --- [b] --- [x] --- [y]

Berarti saat seseorang memasukkan [c]dan [d]ke dalam repositori, jadi ketika Anda menarik Anda mendapatkan grafik sejarah seperti ini:

            [x] --- [y]
           /
[a] --- [b] --- [c] --- [d]

Di sini ada dua kepala dalam satu repositori. Salinan kerja Anda akan selalu mencerminkan satu set perubahan, yang disebut setel induk salinan berfungsi. Periksa ini dengan:

% hg parents

Katakanlah itu melaporkan [y]. Anda dapat melihat kepala dengan

% hg heads

dan ini akan melaporkan [y]dan [d]. Jika Anda ingin memperbarui repositori Anda ke checkout bersih [d], cukup lakukan (gantikan [d]dengan nomor revisi untuk [d]):

% hg update --clean [d]

Anda kemudian akan melihat hg parentslaporan itu [d]. Ini berarti komit Anda berikutnya akan [d]sebagai induk. Dengan demikian Anda dapat memperbaiki bug yang Anda perhatikan di cabang utama dan membuat perubahan [e]:

            [x] --- [y]
           /
[a] --- [b] --- [c] --- [d] --- [e]

Untuk mendorong perubahan [e]hanya, yang perlu Anda lakukan

% hg push -r [e]

di mana [e]hash changeset. Secara default hg pushhanya akan membandingkan repositori dan melihat bahwa [x], [y], dan [e]hilang, tetapi Anda tidak mungkin ingin berbagi [x]dan [y]belum.

Jika perbaikan bug juga mempengaruhi Anda, Anda ingin menggabungkannya dengan cabang fitur Anda:

% hg update [y]
% hg merge

Itu akan membuat grafik repositori Anda terlihat seperti ini:

            [x] --- [y] ----------- [z]
           / /
[a] --- [b] --- [c] --- [d] --- [e]

dimana [z]gabungan antara [y]dan [e]. Anda juga bisa memilih untuk membuang cabang:

% hg strip [x]

Poin utama saya dari cerita ini adalah ini: satu klon dapat dengan mudah mewakili beberapa jalur perkembangan. Ini selalu berlaku untuk "plain hg" tanpa menggunakan ekstensi apa pun. Namun, ekstensi bookmark sangat membantu. Ini akan memungkinkan Anda untuk menetapkan nama (bookmark) ke perubahan. Dalam kasus di atas, Anda ingin bookmark di kepala pengembangan Anda dan satu di kepala hulu. Bookmark dapat didorong dan ditarik dengan Mercurial 1.6 dan telah menjadi fitur bawaan di Mercurial 1.8.

Jika Anda memilih untuk membuat dua klon, klon pengembangan Anda akan terlihat seperti ini setelah membuat [x]dan [y]:

[a] --- [b] --- [x] --- [y]

Dan klon hulu Anda akan berisi:

[a] --- [b] --- [c] --- [d]

Anda sekarang melihat bug dan memperbaikinya. Di sini Anda tidak perlu melakukannya hg updatekarena klon hulu siap digunakan. Anda berkomitmen dan membuat [e]:

[a] --- [b] --- [c] --- [d] --- [e]

Untuk memasukkan perbaikan bug di klon pengembangan Anda, Anda menariknya di sana:

[a] --- [b] --- [x] --- [y]
           \
            [c] --- [d] --- [e]

dan gabungkan:

[a] --- [b] --- [x] --- [y] --- [z]
           \ /
            [c] --- [d] --- [e]

Grafik mungkin terlihat berbeda, tetapi memiliki struktur yang sama dan hasil akhirnya sama. Menggunakan klon, Anda harus melakukan sedikit pembukuan mental.

Cabang yang diberi nama tidak benar-benar masuk ke dalam gambar di sini karena cukup opsional. Mercurial sendiri dikembangkan menggunakan dua klon selama bertahun-tahun sebelum kami beralih menggunakan cabang bernama. Kami mempertahankan cabang yang disebut 'stable' sebagai tambahan dari cabang 'default' dan membuat rilis kami berdasarkan cabang 'stable'. Lihat halaman percabangan standar di wiki untuk deskripsi alur kerja yang direkomendasikan.

Martin Geisler
sumber
1
jika perubahan tersebut berasal dari pengguna yang berbeda, itu akan direkam, jadi menggunakan klon bukanlah hal yang buruk. Saat mendorong Fitur baru, seringkali tidak menarik untuk mengetahui bahwa Anda melakukannya dari repo terpisah. Ada juga ekstensi cabang lokal, yang memberi Anda hanya cabang lokal. Berguna saat mengkloning repo dikaitkan dengan biaya tinggi (waktu / ruang).
Johannes Rudolph
2
merujuk ke: 'klon bagus untuk percobaan cepat' - Tidak, tidak! Bagaimana jika Anda punya beberapa file file dalam repo? Kloning akan memakan waktu lama (kapan saja di atas 1 menit) saat beralih cabang hanya sebentar (<1 detik). Masih menggunakan cabang bernama akan mencemari changelog. Bukankah itu jalan buntu? Atau saya melewatkan sesuatu?
seler
Oke penjual; Kedengarannya seperti modifikasi pada argumen aslinya; Klon bagus jika overhead beberapa salinan lengkap tidak penting bagi Anda, atau ketika Anda dapat menggunakan symlink / hardlink hg untuk mengurangi biaya salinan kerja lokal per cabang yang terpisah.
Warren P
@seler: Anda benar bahwa klon tidak praktis jika kode bade besar. Bookmark adalah solusinya.
Martin Geisler
29

Saya pikir Anda ingin seluruh sejarah dalam satu repo. Memunculkan repo jangka pendek adalah untuk eksperimen jangka pendek, bukan acara besar seperti rilis.

Salah satu kekecewaan Mercurial adalah bahwa tampaknya tidak ada cara mudah untuk membuat cabang yang berumur pendek, bermain dengannya, meninggalkannya, dan mengumpulkan sampah. Cabang selamanya. Saya bersimpati dengan tidak pernah ingin meninggalkan sejarah, tetapi cabang yang super murah dan sekali pakai adalah gitfitur yang saya ingin lihat hg.

Norman Ramsey
sumber
20
Anda dapat dengan mudah membuat cabang fitur seperti itu: "pembaruan hg" ke titik cabang Anda, sunting dan "hg komit". Anda baru saja membuat garis pengembangan yang berbeda - komit baru akan memperluas cabang ini. Gunakan "hg clone -r" untuk menghilangkannya, atau hapus inline dengan "hg strip". Jadi tolong jangan kecewa, atau datang ke milis Mercurial dengan permintaan fitur Anda.
Martin Geisler
8
Sepertinya hg stripitulah yang saya inginkan. Mengapa dokumentasi online mengklaim cabang tidak dapat dihapus?
Norman Ramsey
11
Lihat juga posting blog ini untuk penjelasan tentang bagaimana cabang Mercurial memiliki, dengan cara, lebih murah dari git: stevelosh.com/blog/entry/2009/8/30/…
Martin Geisler
9
Anda dapat menutup cabang bernama dengan hg ci --close-branch.
Andrey Vlasovskikh
3
@Norman Ramsey: ketika orang mengatakan bahwa cabang tidak dapat dihapus, artinya Anda tidak dapat mengubah nama cabang yang disematkan di perubahan tersebut. A mengubah kita tidak pada cabang, itu mendefinisikan cabang. Anda harus menghapus perubahan dan membuatnya kembali dengan nama cabang yang berbeda jika Anda ingin "memindahkannya" ke cabang lain.
Martin Geisler
14

Anda harus melakukan keduanya .

Mulai dengan jawaban yang diterima dari @Norman: Gunakan satu repositori dengan satu cabang bernama per rilis.

Kemudian, miliki satu klon per cabang rilis untuk membangun dan menguji.

Satu catatan utama adalah bahwa bahkan jika Anda menggunakan beberapa repositori, Anda harus menghindari penggunaan transplantuntuk memindahkan set perubahan di antara mereka karena 1) itu mengubah hash, dan 2) itu mungkin memperkenalkan bug yang sangat sulit untuk dideteksi ketika ada perubahan yang bertentangan antara perubahan yang Anda buat. transplantasi dan cabang target. Anda ingin melakukan penggabungan yang biasa (dan tanpa premerge: selalu memeriksa secara visual penggabungan), yang akan menghasilkan apa yang dikatakan @mg di akhir jawabannya:

Grafik mungkin terlihat berbeda, tetapi memiliki struktur yang sama dan hasil akhirnya sama.

Lebih jelasnya, jika Anda menggunakan beberapa repositori, repositori "trunk" (atau default, utama, pengembangan, apa pun) berisi SEMUA perubahan dalam SEMUA repositori. Setiap rilis / repositori cabang hanyalah satu cabang di trunk, semuanya digabung kembali dengan satu cara atau yang lain kembali ke trunk, sampai Anda ingin meninggalkan rilis lama. Oleh karena itu, satu-satunya perbedaan nyata antara repo utama dan repo tunggal dalam skema cabang bernama hanyalah apakah cabang diberi nama atau tidak.

Itu harus menjelaskan mengapa saya mengatakan "mulai dengan satu repo". Repo tunggal itu adalah satu-satunya tempat yang Anda perlukan untuk mencari perubahan apa pun dalam rilis apa pun . Anda bisa lebih lanjut menandai perubahan pada cabang rilis untuk versi. Secara konseptual jelas dan sederhana, dan membuat admin sistem lebih sederhana, karena itu satu-satunya hal yang mutlak harus tersedia dan dapat dipulihkan setiap saat.

Tetapi kemudian Anda masih perlu mempertahankan satu klon per cabang / rilis yang perlu Anda bangun dan uji. Ini sepele yang Anda bisa hg clone <main repo>#<branch> <branch repo>, dan kemudian hg pulldi repo cabang hanya akan menarik perubahan baru pada cabang itu (ditambah perubahan leluhur pada cabang sebelumnya yang digabungkan).

Setup ini paling cocok dengan model komit kernel linux dari penarik tunggal (tidak terasa menyenangkan untuk bertindak seperti Lord Linus. Di perusahaan kami, kami menyebut integrator peran ), karena repo utama adalah satu-satunya hal yang perlu dikloning oleh pengembang dan penarik perlu menarik ke dalam. Pemeliharaan repo cabang murni untuk manajemen rilis dan dapat sepenuhnya otomatis. Pengembang tidak perlu menarik dari / mendorong ke repo cabang.


Berikut ini adalah contoh @ mg yang diolah ulang untuk pengaturan ini. Titik pangkal:

[a] - [b]

Buat cabang bernama untuk versi rilis, katakan "1.0", ketika Anda mendapatkan rilis alpha. Perbaikan bug komit di atasnya:

[a] - [b] ------------------ [m1]
         \                 /
          (1.0) - [x] - [y]

(1.0)bukan perubahan nyata karena cabang bernama tidak ada sampai Anda komit. (Anda dapat membuat komit sepele, seperti menambahkan tag, untuk memastikan cabang bernama dibuat dengan benar.)

Penggabungan [m1]adalah kunci untuk pengaturan ini. Tidak seperti repositori pengembang di mana ada jumlah kepala yang tidak terbatas, Anda TIDAK ingin memiliki beberapa kepala dalam repo utama Anda (kecuali untuk cabang rilis lama yang sudah mati seperti disebutkan sebelumnya). Jadi, setiap kali Anda memiliki perubahan baru pada cabang rilis, Anda harus menggabungkannya kembali ke cabang default (atau cabang rilis berikutnya) segera. Ini menjamin bahwa setiap perbaikan bug dalam satu rilis juga termasuk dalam semua rilis selanjutnya.

Sementara itu pengembangan pada cabang default berlanjut menuju rilis berikutnya:

          ------- [c] - [d]
         /
[a] - [b] ------------------ [m1]
         \                 /
          (1.0) - [x] - [y]

Dan seperti biasa, Anda perlu menggabungkan kedua kepala di cabang default:

          ------- [c] - [d] -------
         /                         \
[a] - [b] ------------------ [m1] - [m2]
         \                 /
          (1.0) - [x] - [y]

Dan ini adalah klon cabang 1.0:

[a] - [b] - (1.0) - [x] - [y]

Sekarang merupakan latihan untuk menambahkan cabang rilis berikutnya. Jika 2.0 maka pasti akan bercabang standar. Jika 1.1, Anda dapat memilih untuk melakukan 1.0 atau default. Terlepas dari itu, setiap perubahan baru pada 1.0 harus terlebih dahulu digabungkan ke cabang berikutnya, kemudian ke default. Ini dapat dilakukan secara otomatis jika tidak ada konflik, hanya menghasilkan penggabungan kosong.


Saya harap contoh ini memperjelas poin saya sebelumnya. Singkatnya, keuntungan dari pendekatan ini adalah:

  1. Repositori otoritatif tunggal yang berisi perubahan lengkap dan riwayat versi.
  2. Manajemen rilis yang jelas dan disederhanakan.
  3. Alur kerja yang jelas dan disederhanakan untuk pengembang dan integrator.
  4. Fasilitasi iterasi alur kerja (ulasan kode) dan otomatisasi (penggabungan kosong otomatis).

UPDATE hg sendiri melakukan ini : repo utama berisi cabang default dan stabil, dan repo stabil adalah klon cabang stabil. Itu tidak menggunakan cabang berversi, karena tag versi di sepanjang cabang stabil cukup baik untuk tujuan manajemen rilisnya.

Geoffrey Zheng
sumber
5

Perbedaan utama, sejauh yang saya tahu, adalah sesuatu yang sudah Anda nyatakan: bernama branched berada dalam satu repositori. Cabang-cabang yang diberi nama memiliki semuanya berguna di satu tempat. Repo terpisah lebih kecil dan mudah untuk dipindahkan. Alasan ada dua aliran pemikiran tentang ini adalah karena tidak ada pemenang yang jelas. Argumen pihak mana pun yang paling masuk akal bagi Anda mungkin adalah argumen yang harus Anda ikuti, karena kemungkinan lingkungan mereka paling mirip dengan Anda.

dwc
sumber
2

Saya pikir ini jelas keputusan pragmatis tergantung pada situasi saat ini, misalnya ukuran fitur / desain ulang. Saya pikir garpu benar-benar bagus untuk kontributor dengan peran yang belum komuter untuk bergabung dengan tim pengembang dengan membuktikan bakat mereka dengan overhead teknis yang terabaikan.

thSoft
sumber
0

Saya benar-benar menyarankan untuk tidak menggunakan cabang bernama untuk versi. Itulah gunanya tag. Cabang yang dinamai dimaksudkan untuk pengalihan yang tahan lama, seperti stablecabang.

Jadi mengapa tidak menggunakan tag saja? Contoh dasar:

  • Pengembangan terjadi pada satu cabang
  • Setiap kali rilis dibuat, Anda menandainya sesuai
  • Pembangunan terus berlanjut dari sana
  • Jika Anda memiliki beberapa bug untuk diperbaiki (atau apa pun) dalam rilis tertentu, Anda cukup memperbarui ke tag itu, buat perubahan dan komit

Itu akan membuat kepala baru yang tidak disebutkan namanya di defaultcabang, alias. cabang anonim, yang sangat bagus di hg. Anda kemudian dapat menggabungkan perbaikan bug komit kembali ke jalur pengembangan utama. Tidak perlu untuk cabang bernama.

DanMan
sumber
Ini sangat tergantung pada proses Anda. Aplikasi web, misalnya, berfungsi dengan baik dengan hierarki cabang stabil / pengujian / devel. Saat membangun perangkat lunak desktop, kami biasanya memiliki cabang pengembangan (default) serta 1-3 cabang yang berbeda dalam pemeliharaan. Sulit untuk diprediksi ketika kita mungkin perlu mengunjungi kembali cabang, dan ada keanggunan tertentu tentang memiliki cabang melacak versi mayor.
James Emerton