2012-03-16 19 views
11

Tôi đang cố gắng xây dựng “ bản đồ nhiệt ” từ lịch sử nhiều năm được lưu trữ trong kho lưu trữ git nơi đơn vị chi tiết là các chức năng riêng lẻ. Chức năng sẽ phát triển nóng hơn khi chúng thay đổi nhiều lần hơn, thường xuyên hơn và với nhiều dòng không trống thay đổi.Làm cách nào để liên kết các dòng đã thay đổi với các hàm trong kho git của mã C?

Như một sự khởi đầu, tôi đã kiểm tra đầu ra của

git log --patch -M --find-renames --find-copies-harder --function-context -- *.c 

Tôi nhìn bằng Language.C từ Hackage, nhưng nó dường như muốn có một bản dịch hoàn chỉnh đơn vị — mở rộng tiêu đề và tất cả — thay vì việc có thể để đối phó với một đoạn mã nguồn.

Tùy chọn --function-context là mới kể từ phiên bản 1.7.8. Nền tảng của việc thực hiện in v1.7.9.4 is a regex:

PATTERNS("cpp", 
     /* Jump targets or access declarations */ 
     "!^[ \t]*[A-Za-z_][A-Za-z_0-9]*:.*$\n" 
     /* C/++ functions/methods at top level */ 
     "^([A-Za-z_][A-Za-z_0-9]*([ \t*]+[A-Za-z_][A-Za-z_0-9]*([ \t]*::[ \t]*[^[:space:]]+)?){1,}[ \t]*\\([^;]*)$\n" 
     /* compound type at top level */ 
     "^((struct|class|enum)[^;]*)$", 
     /* -- */ 
     "[a-zA-Z_][a-zA-Z0-9_]*" 
     "|[-+0-9.e]+[fFlL]?|0[xXbB]?[0-9a-fA-F]+[lL]?" 
     "|[-+*/<>%&^|=!]=|--|\\+\\+|<<=?|>>=?|&&|\\|\\||::|->"), 

Điều này dường như nhận ra ranh giới khá tốt nhưng doesn t ’ luôn rời khỏi chức năng như dòng đầu tiên của hunk diff, ví dụ, với chỉ thị #include ở trên cùng hoặc với một đoạn có chứa nhiều định nghĩa chức năng. Một tùy chọn để phân biệt để phát ra các khối riêng biệt cho mỗi hàm được thay đổi sẽ thực sự hữu ích.

This isn ’ t an toàn quan trọng, vì vậy tôi có thể chịu đựng một số lỗi. Điều đó có nghĩa là tôi có thể có Zawinski ’ s “two problems”?

+2

Câu hỏi rất thú vị! Cuối cùng một cái gì đó khác hơn là "Tôi mới đến git, làm thế nào để tôi hoàn tác một cam kết?". Chờ cho pháo hạng nặng diễu hành (tức là VonC) :) – ralphtheninja

+0

Bạn vui lòng xác nhận ngôn ngữ nguồn là C hoặc C++? – gbulmer

+0

@gbulmer Ngôn ngữ nguồn là C. 'git diff' sử dụng cùng một mẫu bên trong để nhận ra các định nghĩa hàm trong C và C++. –

Trả lời

1

Tôi nhận thấy đề xuất này là một chút tiếp tuyến, nhưng nó có thể giúp để làm rõ và xếp hạng các yêu cầu. Điều này sẽ làm việc cho C hoặc C++ ...

Thay vì cố gắng tìm khối văn bản có chức năng và so sánh chúng, hãy sử dụng trình biên dịch để tạo khối nhị phân. Cụ thể, đối với mọi tệp nguồn C/C++ trong một tập thay đổi, biên dịch nó thành một đối tượng. Sau đó, sử dụng mã đối tượng làm cơ sở để so sánh.

Điều này có thể không khả thi đối với bạn, nhưng IIRC có một tùy chọn trên gcc để biên dịch sao cho mỗi hàm được biên dịch thành 'đoạn độc lập' trong tệp mã đối tượng được tạo. Trình liên kết có thể kéo từng 'đoạn' vào một chương trình. (Nó khá trễ ở đây, vì vậy tôi sẽ xem xét nó vào buổi sáng, nếu bạn quan tâm đến ý tưởng này.)

Vì vậy, giả sử chúng ta có thể làm điều này, bạn sẽ có rất nhiều hàm được xác định bởi các khối mã nhị phân, do đó, so sánh 'nhiệt' đơn giản là 'mã dài hơn hoặc ngắn hơn giữa các phiên bản cho bất kỳ hàm nào?'

Tôi cũng nghĩ rằng nó có thể là thực tế để sử dụng objdump để reconstitute lắp ráp cho các chức năng. Tôi có thể sử dụng một số biểu thức thông thường ở giai đoạn này để cắt bỏ các tên đăng ký, do đó thay đổi để phân bổ đăng ký không gây ra quá nhiều dương tính giả (thay đổi). Tôi có thể thậm chí cố gắng sắp xếp các hướng dẫn lắp ráp trong các cơ quan chức năng, và phân biệt chúng để có được một mẫu "gỡ bỏ" so với "được thêm vào" giữa hai triển khai thực hiện chức năng. Điều này sẽ đưa ra một thước đo thay đổi khá độc lập với bố cục và thậm chí phần nào độc lập với thứ tự của một số nguồn.

Vì vậy, có thể thú vị khi xem hai triển khai thay thế có cùng chức năng (tức làtừ một tập hợp thay đổi khác nhau) là các hướng dẫn tương tự :-)

Cách tiếp cận này cũng nên hoạt động đối với C++ vì tất cả các tên đã bị xáo trộn một cách thích hợp, nên đảm bảo các chức năng giống nhau đang được so sánh.

Vì vậy, các biểu thức thông thường có thể được giữ rất đơn giản :-)

Giả sử tất cả những điều này là đơn giản, những gì có thể phương pháp này thất bại trong việc cung cấp cho bạn?

Lưu ý bên: Chiến lược cơ bản này có thể hoạt động cho bất kỳ ngôn ngữ nào nhắm mục tiêu đến mã máy, cũng như các bộ lệnh VM như mã Java VM Bytecode, mã .NET CLR, v.v.

+0

Điều này sẽ bị loại bỏ bởi các thiết lập nội tuyến và tối ưu hóa –

+0

Đó là một cách tiếp cận thú vị. Repo cụ thể này phụ thuộc vào một thư viện trong repo khác, vì vậy tôi phải quay trở lại thông qua cả hai lịch sử ở các mức khác nhau để cố gắng giữ cho client repo compilable (* tức là *, các khai báo thích hợp và các tiêu đề có sẵn). –

+0

@Ben Voigt - Tôi đã giả định các tùy chọn biên dịch sẽ giống nhau, và điều đó phải đơn giản để sắp xếp. Tôi giả định trình biên dịch không quá hỗn loạn (theo nghĩa fractal). Bằng cách sắp xếp các opcodes trong một hàm, và xóa các tên đăng ký, sự khác biệt trong mã thực tế sẽ cho biết sự thay đổi "hiệu quả" đã xảy ra như thế nào. Điều này không hoàn hảo, nhưng IMHO là một lựa chọn thú vị để so sánh văn bản.Đối với các tùy chọn trình biên dịch tương tự, các hàm thực sự không thay đổi ở mức mã được tạo ra, nhưng đã có các thay đổi văn bản, cũng có thể là một phân tích thú vị. – gbulmer

0

Có thể đáng xem xét việc xây dựng một trình phân tích cú pháp đơn giản, sử dụng một trong các công cụ phổ biến, thay vì chỉ sử dụng các biểu thức chính quy. Rõ ràng là tốt hơn nên chọn thứ bạn quen thuộc hoặc tổ chức của bạn đã sử dụng.

Đối với vấn đề này, trình phân tích cú pháp không thực sự cần xác thực mã (tôi cho rằng nó hợp lệ khi được kiểm tra) và không cần phải hiểu mã, vì vậy nó có thể khá ngu ngốc.

Nó có thể vứt bỏ nhận xét (giữ lại dòng mới), bỏ qua nội dung của chuỗi văn bản và xử lý văn bản chương trình theo cách rất đơn giản. Nó chủ yếu cần theo dõi cân bằng '{' '}', cân bằng '(' ')' và tất cả văn bản chương trình hợp lệ khác chỉ là các mã thông báo riêng lẻ có thể được truyền qua 'thông qua'.

Đầu ra của nó có thể là một tệp/chức năng riêng biệt để giúp việc theo dõi trở nên dễ dàng hơn.

Nếu ngôn ngữ là C hoặc C++ và các nhà phát triển có kỷ luật hợp lý, họ có thể không bao giờ sử dụng 'macro phi cú pháp'. Nếu đúng như vậy, thì các tệp không cần phải được xử lý trước.

Sau đó, một phân tích cú pháp chủ yếu chỉ tìm kiếm một tên hàm (một định danh) ở phạm vi tập tiếp theo (tham số-list) {...} mã ...

tôi muốn nó sẽ là swag một vài ngày làm việc bằng cách sử dụng yacc & lex/flex & bò rừng, và nó có thể đơn giản đến mức chúng không cần cho trình tạo trình phân tích cú pháp.

Nếu mã là Java, thì ANTLR là có thể và tôi nghĩ rằng có một ví dụ về trình phân tích cú pháp Java đơn giản.

Nếu Haskell là trọng tâm của bạn, chúng có thể là các dự án của sinh viên được xuất bản đã tạo ra một cú đâm hợp lý tại trình phân tích cú pháp.

+0

Điều đó nghe có vẻ tương tự như cách tiếp cận tôi đã phác thảo. Tôi muốn biết phạm vi của các dòng trong hunk khác thuộc về một định nghĩa hàm đã cho. Phù hợp với các dấu ngoặc nhọn ngoài cùng là khó sử dụng vì các dấu ngoặc không cân bằng thêm do các dòng được thêm vào hoặc loại bỏ. –

+0

@Greg Bacon - ah! Tôi nghĩ tôi đã hiểu! Tôi đang nghĩ đến cách tiếp cận khác, nhưng tôi cần thứ gì đó để ăn. Tôi có thể đề xuất một 'cách thứ ba'? – gbulmer

Các vấn đề liên quan